使用channel代替條件變數

themoonstone發表於2016-10-14

條件變數訊號傳遞是將條件傳達給一個或多個併發任務的方式,在 Go 標準庫中'sync'包提供了使用條件變數傳送訊號的方法。除此之外、在 golang 中另一種方式是使用內建的 channel
兩種方式都是暫停當前正在執行的 goroutine 並將 CPU 出讓給另一個 goroutine、在此我解釋一下為什麼我更偏向於使用 channel 而不是條件變數傳播訊號到我的 worker goroutines。 使用條件變數遇到的問題: 條件變數的兩個解除阻塞方法: 1、喚醒一個單一的等待 goroutine 2、喚醒一個所有的等待 goroutine 示例如下:

c := sync.NewCond(&sync.Mutex{})
go func() {
    c.L.Lock()
    c.Wait()
    c.L.Unlock()
    fmt.Println("DONE")
}()
c.Signal()

理論上,goroutine 將在條件建立訊號時喚醒。一旦發生這種情況,我們應該看到一個字串'DONE'出現、但是由於當前的作業系統是搶佔式的,所以我們不能保證 s.Signal() 一定會在 goroutine 被完全初始化和阻塞之後才呼叫 (無法保證程式執行過程中各個 goroutine 的執行順序)、實際上,對 c.Signal() 的呼叫可以在 Wait() 之前執行,此時程式將退出、我們什麼也看不到、如果我們正在執行的時一個服務、將會有一個 goroutine 處於阻塞狀態、該 goroutine 可能會一直阻塞、直到有訊號來喚醒它。之所以會出現這樣的情況是因為阻塞的 goroutine 可能會錯過 Signal() 或者 Broadcast() 方法的喚醒。 而如果你的程式是一個必須 7*24 小時不間斷執行的服務的話、這樣的情況會使得你的程式 goroutine 持續保持增長直到記憶體 “爆炸”,而你的作業系統最終會因為記憶體溢位而終止該程式 通過 channels 在併發 goroutine 中進行資訊傳遞則可以有效避免這樣的情況、因為 channel 可以通過超時機制終止 goroutine 執行 正常情況下,每次計算機的操作都需要一段時間,它們會在特定的(也許很少的,也許是頻繁的)條件下包含並應該包含停止自身執行的機制。使用 channel 的情況下,我們可以使用 select 語句並新增超時機制。

readyChan := make(chan bool, 1)
go func() {
    select {
        case <- readyChan:
            fmt.Println("HELLO")
        case <- time.After(time.Minute):
    }
}()
readyChan <- true

如程式碼所示、在使用 channel 的情況下、不會出現 goroutine 一直堆積的情況、如果因為某些原因導致阻塞程式沒有被喚醒 、則在一分鐘的超時期到的時候、goroutine 會優雅地終止 有一些情況,我們想結束所有等待 goroutines。幸運的是,channel 可以關閉,並保持此狀態,以便進一步嘗試從中讀取:

readyChan := make(chan bool, 1)
for i := 0; i < 5; i++ {
    go func() {
        _, ok := <-readyChan
        if !ok {
            fmt.Println("Worker ending ...")
            return
        }
    }()
}
close(readyChan)
time.Sleep(3*time.Second)

三秒鐘後,我們將看到嘗試從 channel 中讀取資料的五個 goroutine 返回,因為主 goroutine 剛剛關閉了該 channel。 為什麼要使用緩衝通道? 這兩個示例都使用緩衝 channel,因為如果等待 goroutine 需要很長時間來初始化而且 channel 沒有緩衝,傳送 goroutine 將鎖定並等待,直到 channel 上有一個監聽器。 原文連結:https://zeta.si/page/Using-Go-Channels-Instead-Of-Conditions

更多原創文章乾貨分享,請關注公眾號
  • 使用channel代替條件變數
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章