hi,你們好,我是 haohongfan。web
本篇文章會從源碼角度去深刻剖析下 sync.Cond。Go 平常開發中 sync.Cond 多是咱們用的較少的控制併發的手段,由於大部分場景下都被 Channel 代替了。還有就是 sync.Cond 使用確實也蠻複雜的。微信
好比下面這段代碼:併發
package main
import (
"fmt"
"time"
)
func main() {
done := make(chan int, 1)
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 惟一有用武之地的地方。函數
先列出來一些問題吧,能夠帶着這些問題來閱讀本文:學習
-
cond.Wait自己就是阻塞狀態,爲何 cond.Wait 須要在循環內 ? -
sync.Cond 如何觸發不能複製的 panic ? -
爲何 sync.Cond 不能被複制 ? -
cond.Signal 是如何通知一個等待的 goroutine ? -
cond.Broadcast 是如何通知等待的 goroutine 的?
源碼剖析
sync.Cond 排隊動圖flex
cond.Wait 是阻塞的嗎?是如何阻塞的?
是阻塞的。不過不是 sleep 這樣阻塞的。spa
調用 goparkunlock
解除當前 goroutine 的 m 的綁定關係,將當前 goroutine 狀態機切換爲等待狀態。等待後續 goready 函數時候可以恢復現場。.net
cond.Signal 是如何通知一個等待的 goroutine ?
-
判斷是否有沒有被喚醒的 goroutine,若是都已經喚醒了,直接就返回了 -
將已通知 goroutine 的數量加1 -
從等待喚醒的 goroutine 隊列中,獲取 head 指針指向的 goroutine,將其從新加入調度 -
被阻塞的 goroutine 能夠繼續執行
cond.Broadcast 是如何通知等待的 goroutine 的?
-
判斷是否有沒有被喚醒的 goroutine,若是都已經喚醒了,直接就返回了 -
將等待通知的 goroutine 數量和已經通知過的 goroutine 數量設置成相等 -
遍歷等待喚醒的 goroutine 隊列,將全部的等待的 goroutine 都從新加入調度 -
全部被阻塞的 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。