這一次,完全搞懂 Go Cond

hi,你們好,我是 haohongfan。web

本篇文章會從源碼角度去深刻剖析下 sync.Cond。Go 平常開發中 sync.Cond 多是咱們用的較少的控制併發的手段,由於大部分場景下都被 Channel 代替了。還有就是 sync.Cond 使用確實也蠻複雜的。微信

好比下面這段代碼:併發

package main

import (
 "fmt"
 "time"
)

func main() {
 done := make(chan int1)

 go func() {
  time.Sleep(5 * time.Second)
  done <- 1
 }()

 fmt.Println("waiting")
 <-done
 fmt.Println("done")
}

一樣可使用 sync.Cond 來實現編輯器

package main

import (
 "fmt"
 "sync"
 "time"
)

func main() {
 cond := sync.NewCond(&sync.Mutex{})
 var flag bool
 go func() {
  time.Sleep(time.Second * 5)
  cond.L.Lock()
  flag = true
  cond.Signal()
  cond.L.Unlock()
 }()

 fmt.Println("waiting")
 cond.L.Lock()
 for !flag {
  cond.Wait()
 }
 cond.L.Unlock()
 fmt.Println("done")
}

大部分場景下使用 channel 是比 sync.Cond方便的。不過咱們要注意到,sync.Cond 提供了 Broadcast 方法,能夠通知全部的等待者。想利用 channel 實現這個方法仍是不容易的。我想這應該是 sync.Cond 惟一有用武之地的地方。函數

先列出來一些問題吧,能夠帶着這些問題來閱讀本文:學習

  1. cond.Wait自己就是阻塞狀態,爲何 cond.Wait 須要在循環內 ?
  2. sync.Cond 如何觸發不能複製的 panic ?
  3. 爲何 sync.Cond 不能被複制 ?
  4. cond.Signal 是如何通知一個等待的 goroutine ?
  5. cond.Broadcast 是如何通知等待的 goroutine 的?

源碼剖析

sync.cond wait

sync.Cond Signal

sync.Cond Broadcast

sync.Cond 排隊動圖flex

cond.Wait 是阻塞的嗎?是如何阻塞的?

是阻塞的。不過不是 sleep 這樣阻塞的。spa

調用 goparkunlock 解除當前 goroutine 的 m 的綁定關係,將當前 goroutine 狀態機切換爲等待狀態。等待後續 goready 函數時候可以恢復現場。.net

cond.Signal 是如何通知一個等待的 goroutine ?

  1. 判斷是否有沒有被喚醒的 goroutine,若是都已經喚醒了,直接就返回了
  2. 將已通知 goroutine 的數量加1
  3. 從等待喚醒的 goroutine 隊列中,獲取 head 指針指向的 goroutine,將其從新加入調度
  4. 被阻塞的 goroutine 能夠繼續執行

cond.Broadcast 是如何通知等待的 goroutine 的?

  1. 判斷是否有沒有被喚醒的 goroutine,若是都已經喚醒了,直接就返回了
  2. 將等待通知的 goroutine 數量和已經通知過的 goroutine 數量設置成相等
  3. 遍歷等待喚醒的 goroutine 隊列,將全部的等待的 goroutine 都從新加入調度
  4. 全部被阻塞的 goroutine 能夠繼續執行

cond.Wait自己就是阻塞狀態,爲何 cond.Wait 須要在循環內 ?

咱們能注意到,調用 cond.Wait 的位置,使用的是 for 的方式來調用 wait 函數,而不是使用 if 語句。指針

這是因爲 wait 函數被喚醒時,存在虛假喚醒等狀況,致使喚醒後發現,條件依舊不成立。所以須要使用 for 語句來循環地進行等待,直到條件成立爲止。

使用中注意點

1. 不能不加鎖直接調用 cond.Wait

func (c *Cond) Wait() {
 c.checker.check()
 t := runtime_notifyListAdd(&c.notify)
 c.L.Unlock()
 runtime_notifyListWait(&c.notify, t)
 c.L.Lock()
}

咱們看到 Wait 內部會先調用 c.L.Unlock(),來先釋放鎖。若是調用方不先加鎖的話,會觸發「fatal error: sync: unlock of unlocked mutex」。關於 mutex 的使用方法,推薦閱讀下《這多是最容易理解的 Go Mutex 源碼剖析》

2. 爲何不能 sync.Cond 不能複製 ?

sync.Cond 不能被複制的緣由,並非由於 sync.Cond 內部嵌套了 Locker。由於 NewCond 時傳入的 Mutex/RWMutex 指針,對於 Mutex 指針複製是沒有問題的。

主要緣由是 sync.Cond 內部是維護着一個 notifyList。若是這個隊列被複制的話,那麼就在併發場景下致使不一樣 goroutine 之間操做的 notifyList.wait、notifyList.notify 並非同一個,這會致使出現有些 goroutine 會一直堵塞。

這裏留下一個問題,sync.Cond 內部是有一段代碼 check sync.Cond 是不能被複制的,下面這段代碼能觸發這個 panic 嗎?

package main

import (
 "fmt"
 "sync"
)

func main() {
 cond1 := sync.NewCond(new(sync.Mutex))
 cond := *cond1
 fmt.Println(cond)
}

有興趣的能夠動手嘗試下,以及嘗試下如何才能觸發這個panic "sync.Cond is copied」 。

sync.Cond 的剖析到這裏基本就結束了。有什麼想跟我交流的,歡迎評論區留言。


歡迎關注個人公衆號,隨時關注個人動態

sync.Cond 完整流程圖獲取連接:連接:https://pan.baidu.com/s/1DjtfCyCua0nGYB6TOSsYWA  密碼: wn02。其餘模塊流程圖,請關注公衆號回覆1獲取。

學習資料分享,關注公衆號回覆指令:

  • 回覆 0,獲取 《Go 面經》
  • 回覆 1,獲取 《Go 源碼流程圖》

本文分享自微信公衆號 - HHFCodeRv(hhfcodearts)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索