首頁 >後端開發 >Golang >透過對通道進行多次寫入來理解 golang 阻塞通道行為

透過對通道進行多次寫入來理解 golang 阻塞通道行為

WBOY
WBOY轉載
2024-02-09 14:27:20792瀏覽

通过对通道进行多次写入来理解 golang 阻塞通道行为

php小編柚子在這篇文章中將向大家介紹如何透過對通道進行多次寫入來理解 golang 阻塞通道的行為。在golang中,通道是一種用於在協程之間傳遞資料的重要機制。當頻道已滿時,寫入操作會被阻塞,直到頻道有空閒位置。我們將透過一個簡單的範例來示範這種行為,並解釋阻塞通道的原理和使用方法。無論是初學者或有經驗的golang開發者,都能從本文中獲得有益的知識和實務經驗。讓我們開始吧!

問題內容

我是 golang 新手,正在嘗試了解該語言中的並發性。我有一個程式碼,可以將一些值推送到通道,然後讀取它們。

package main

import (
    "log"
    "time"
)

func Greet2(c chan string) {
    // logging to Stdout is not an atomic operation
    // so artificially, sleep for some time
    time.Sleep(2 * time.Second)
    
    // 5. until below line reads and unblock the channel
    log.Printf("5. Read Greet2:: %s\n\n", <-c)
}

func Greet(c chan string) {
    // 4. Push a new value to the channel, this will block
    // Process will look for other go routines to execute
    log.Printf("4. Add 'Greet::John' to the channel, block until it is read. Remember, 'Greet' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.\n\n")
    c <- "Greet::John!"
    
    // 8. This statement will never execute
    log.Printf("8. Read Greet:: %s !\n\n", <-c)
}

func main() {

    c := make(chan string)

    log.Println("1. Main start")
    
    // 2. Both go routine will be declared and both will
    // for a value to be inserted in the channel
    log.Println("2. Declare go routines.\n\n")
    go Greet(c)
    go Greet2(c)
    
    // 3. write will block
    log.Println("3. Add 'main::Hello' to the channel, block until it is read. Remember, 'main' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.\n\n")
    c <- "main::Hello"
    
    // Sleep to give time goroutines to execute
    time.Sleep(time.Second)
    
    // 6. read the channel value.
    log.Printf("6. Read main:: %s \n\n", <-c)
    
    // 7. Insert a new value to the channel
    log.Println("7. Add 'main::Bye' to the channel, block until it is read.\n")
    c <- "main::Bye"
    
    // Sleep to give time goroutines to execute
    time.Sleep(time.Second)
    log.Println("9. Main stop")

}

上述程式的輸出是

2023/09/02 21:58:07 1. Main start
2023/09/02 21:58:07 2. Declare go routines.


2023/09/02 21:58:07 3. Add 'main::Hello' to the channel, block until it is read. Remember, 'main' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.


2023/09/02 21:58:07 4. Add 'Greet::John' to the channel, block until it is read. Remember, 'Greet' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.

2023/09/02 21:58:10 5. Read Greet2:: main::Hello

2023/09/02 21:58:11 6. Read main:: Greet::John!

2023/09/02 21:58:11 7. Add 'main::Bye' to the channel, block until it is read.

2023/09/02 21:58:11 8. Read Greet:: main::Bye !

2023/09/02 21:58:12 9. Main stop

我無法理解為什麼4.(另一個寫入通道)在5.(第一次從通道讀取)之前執行,因為3. 將阻塞,並且在讀取值之前通道不可用來自它(在步驟5. 中)。我是否誤解了阻塞行為,在步驟3. 中,只有main goroutine 區塊和Greet (在步驟4. 中)可以向通道寫入附加價值?一個解釋確實可以解決我的困惑:)

乾杯, DD。

感謝您的回复,我已經創建了一個更簡單的程式來演示。並行

package main

import (
        "fmt"
)

func do2(c chan int) {
        fmt.Println(<-c)
}

func do(c chan int) {
        // 4. this statement is trying to write another value "2" to the channel
        // Channel already contains "1" as the value which has not been read yet.
        // this statement will wait for "1" to get read and block the execution.
        // Scheduler will look for other goroutines that can execute.
        // However, this("do") is blocked as well as "main" is blocked too and
        // there are no other goroutines to execute.
        // Hence, will result in a "Deadlock" fatal error.
        c <- 2
        fmt.Println(<-c)
}

func main() {

        // 1. Declare a channel
        c := make(chan int)
        // 2. Declare "do" goroutine
        go do(c)
        // 3. write "1" to the channel
        // This will block and wait for program's other goroutines to read the value.
        // however, there is only "do" goroutine is defined can run at this point.
        // Scheduler, will try to run "do" goroutine.
        c <- 1
        go do2(c)

}

死鎖可以透過交換c <- 1go do2(c)語句來修復。

解決方法

在Go 中,當您在通道上發送值時(步驟3 中的c <- "main::Hello"),發送Goroutine 將阻塞,直到有另一個Goroutine 準備好從通道接收值。然而,這並不意味著沒有其他 goroutine 可以繼續執行。在您的程式碼中, GreetGreet2 協程都在等待來自通道的值,因此當您在步驟3 中發送值時,其中一個(不保證是哪一個)將解除阻塞並繼續執行。

讓我一步一步分解事件的順序:

  1. 主程式啟動,您建立一個頻道 c
  2. 您宣告了兩個 goroutine,GreetGreet2,並且兩者都在等待來自通道的值。
  3. 您在通道上發送一個值“main::Hello”,這會阻塞主 goroutine,直到其他 goroutine 從通道讀取資料。但是,兩個 Goroutine 之一(GreetGreet2)不會被阻止接收該值。
  4. Greet 解除阻塞並繼續執行。它記錄訊息“4. 將‘Greet::John’加入頻道...”並發送“Greet::John!”在頻道上。這會再次阻塞 Greet,因為此時沒有其他 goroutine 可以從通道中讀取。
  5. Greet2 解除阻塞並繼續執行。它記錄訊息“5. Read Greet2:: main::Hello”並從通道讀取值“main::Hello”。
  6. Main 解鎖,記錄「6. Read main:: Greet::John!」並寫著「問候::約翰!」來自頻道。
  7. Main 在頻道上發送另一個值「main::Bye」。此時,Greet 在寫入通道時仍被阻止,而 Greet2 因未從通道讀取而被阻止。
  8. 由於 Greet 仍然在寫入時被阻止,因此它永遠不會記錄「8. Read Greet:: main::Bye !」
  9. 主要站點。

因此,理解此處行為的關鍵是,當您在通道上發送值時,它會解鎖任何正在等待從通道讀取資料的 goroutine。等待的 goroutine 解除阻塞的順序是不確定的,取決於調度程序。在您的情況下,Greet2 碰巧首先被解鎖,但也可能是 Greet

總之,您觀察到的行為與 Go 通道的工作方式完全一致,需要注意的是,競爭 Goroutines 之間的執行順序無法保證。

以上是透過對通道進行多次寫入來理解 golang 阻塞通道行為的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:stackoverflow.com。如有侵權,請聯絡admin@php.cn刪除