溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

如何理解Go Cond

發(fā)布時(shí)間:2021-10-12 15:05:37 來(lái)源:億速云 閱讀:155 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹“如何理解Go Cond”,在日常操作中,相信很多人在如何理解Go Cond問題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”如何理解Go Cond”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

比如下面這段代碼:

package mainimport (
 "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 來(lái)實(shí)現(xiàn)

package mainimport (
 "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")
}

大部分場(chǎng)景下使用 channel 是比 sync.Cond方便的。不過(guò)我們要注意到,sync.Cond 提供了 Broadcast 方法,可以通知所有的等待者。想利用 channel 實(shí)現(xiàn)這個(gè)方法還是不容易的。我想這應(yīng)該是 sync.Cond 唯一有用武之地的地方。

先列出來(lái)一些問題吧,可以帶著這些問題來(lái)閱讀本文:

  1. cond.Wait本身就是阻塞狀態(tài),為什么 cond.Wait 需要在循環(huán)內(nèi) ?

  2. sync.Cond 如何觸發(fā)不能復(fù)制的 panic ?

  3. 為什么 sync.Cond 不能被復(fù)制 ?

  4. cond.Signal 是如何通知一個(gè)等待的 goroutine ?

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

源碼剖析

如何理解Go Cond

sync.cond wait

如何理解Go Cond

sync.Cond Signal

如何理解Go Cond

sync.Cond Broadcast

如何理解Go Cond

sync.Cond 排隊(duì)動(dòng)圖

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

是阻塞的。不過(guò)不是 sleep 這樣阻塞的。

調(diào)用 goparkunlock 解除當(dāng)前 goroutine 的 m 的綁定關(guān)系,將當(dāng)前 goroutine 狀態(tài)機(jī)切換為等待狀態(tài)。等待后續(xù) goready 函數(shù)時(shí)候能夠恢復(fù)現(xiàn)場(chǎng)。

cond.Signal 是如何通知一個(gè)等待的 goroutine ?

  1. 判斷是否有沒有被喚醒的 goroutine,如果都已經(jīng)喚醒了,直接就返回了

  2. 將已通知 goroutine 的數(shù)量加1

  3. 從等待喚醒的 goroutine 隊(duì)列中,獲取 head 指針指向的 goroutine,將其重新加入調(diào)度

  4. 被阻塞的 goroutine 可以繼續(xù)執(zhí)行

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

  1. 判斷是否有沒有被喚醒的 goroutine,如果都已經(jīng)喚醒了,直接就返回了

  2. 將等待通知的 goroutine 數(shù)量和已經(jīng)通知過(guò)的 goroutine 數(shù)量設(shè)置成相等

  3. 遍歷等待喚醒的 goroutine 隊(duì)列,將所有的等待的 goroutine 都重新加入調(diào)度

  4. 所有被阻塞的 goroutine 可以繼續(xù)執(zhí)行

cond.Wait本身就是阻塞狀態(tài),為什么 cond.Wait 需要在循環(huán)內(nèi) ?

我們能注意到,調(diào)用 cond.Wait 的位置,使用的是 for 的方式來(lái)調(diào)用 wait 函數(shù),而不是使用 if 語(yǔ)句。

這是由于 wait 函數(shù)被喚醒時(shí),存在虛假喚醒等情況,導(dǎo)致喚醒后發(fā)現(xiàn),條件依舊不成立。因此需要使用 for 語(yǔ)句來(lái)循環(huán)地進(jìn)行等待,直到條件成立為止。

使用中注意點(diǎn)

1. 不能不加鎖直接調(diào)用 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 內(nèi)部會(huì)先調(diào)用 c.L.Unlock(),來(lái)先釋放鎖。如果調(diào)用方不先加鎖的話,會(huì)觸發(fā)“fatal error: sync: unlock of unlocked mutex”。關(guān)于 mutex 的使用方法,推薦閱讀下《這可能是最容易理解的 Go Mutex 源碼剖析》

2. 為什么不能 sync.Cond 不能復(fù)制 ?

sync.Cond 不能被復(fù)制的原因,并不是因?yàn)?sync.Cond 內(nèi)部嵌套了 Locker。因?yàn)?NewCond 時(shí)傳入的 Mutex/RWMutex 指針,對(duì)于 Mutex 指針復(fù)制是沒有問題的。

主要原因是 sync.Cond 內(nèi)部是維護(hù)著一個(gè) notifyList。如果這個(gè)隊(duì)列被復(fù)制的話,那么就在并發(fā)場(chǎng)景下導(dǎo)致不同 goroutine 之間操作的 notifyList.wait、notifyList.notify 并不是同一個(gè),這會(huì)導(dǎo)致出現(xiàn)有些 goroutine 會(huì)一直堵塞。

這里留下一個(gè)問題,sync.Cond 內(nèi)部是有一段代碼 check sync.Cond 是不能被復(fù)制的,下面這段代碼能觸發(fā)這個(gè) panic 嗎?

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

有興趣的可以動(dòng)手嘗試下,以及嘗試下如何才能觸發(fā)這個(gè)panic "sync.Cond is copied” 。

到此,關(guān)于“如何理解Go Cond”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI