Go語言sync.Cond使用方法詳解
概述
每一個sync.Cond
結(jié)構(gòu)體在初始化時都需要傳入一個互斥鎖,我們可以通過下面的例子了解它的使用方法:
var status int64 func main(){ c := sync.NewCond(&sync.mutex{}) for i := 0; i < 10; i++ { go listen(c) } time.Sleep(1 * time.Second) go broadcast(c) ch := make(chan os.Signal, 1) signal.Notify(ch, os.Interrupt) <-ch } func broadcast(c *sync.Cond) { c.L.Lock() atomic.StoreInt64(&status, 1) c.Broadcast() c.L.Unlock() } func listen(c *sync.Cond) { c.L.Lock() for atomic.LoadInt64(&status) != 1 { c.Wait() } fmt.Println("listen") c.L.Unlock() }
運行結(jié)果:
listen
...
listen
上述代碼同時運行了 11 個Goroutine
,它們分別做了不同事情:
- 10個
Goroutine
通過sync.Cond.Wait
等待特定條件滿足 - 1個
Goroutine
會調(diào)用sync.Cond.Broadcast
喚醒所有陷入等待的Goroutine
調(diào)用sync.Cond.Broadcast
方法后,上述代碼會打印出10次 "listen" 并結(jié)束調(diào)用。
結(jié)構(gòu)體
sync.Cond
的結(jié)構(gòu)體中包含以下 4 個字段:
type Cond struct { noCopy noCopy L Locker notify notifyList checker copyChecker }
- noCopy —— 用于保證結(jié)構(gòu)體不會在編譯期間復(fù)制
- L —— 用于保護內(nèi)部的
notify
字段,Locker
接口類型的變量 - notify —— 一個
Goroutine
的鏈表,它是實現(xiàn)同步機制的核心結(jié)構(gòu) - copyChecker —— 用于禁止運行期間發(fā)生的復(fù)制
type notifyList struct { wait uint32 notify uint32 lock mutex head *sudog tail *sudog }
在sync.notifyList
結(jié)構(gòu)體中,head
和tail
分別指向鏈表的頭和尾,wait
和notify
分別表示當(dāng)前正在等待的和已經(jīng)通知的Goroutine
的索引。
接口
sync.Cond
對外暴露的sync.Cond.Wait
方法會令當(dāng)前Goroutine
陷入休眠狀態(tài),它的執(zhí)行過程分成以下兩個步驟:
- 調(diào)用
runtime.notifyListAdd
將等待計時器加一并解鎖 - 調(diào)用
runtime.notifyListWait
等待其他Goroutine
被喚醒并對其加鎖
func (c *Cond) Wait () { c.checker.check() t := runtime_notifyListAdd(&c.notify) // runtime.notifyListAdd 的鏈接名 c.L.Unlock() runtime_notifyListWait(&c.notify, t) //runtime.notifyListWait 的鏈接名 c.L.Lock() } func notifyListAdd(l *notifyList) uint32 { return atomic.Xadd(&l.wait, 1) - 1 }
runtime.notifyListWait
會獲取當(dāng)前Goroutine
并將它追加到Goroutine
通知鏈表的末端:
func notifyListWait(l *notifyList, t uint32) { s := acquireSudog() s.g = getg() s.ticket = t if l.tail == nil { l.head = s } else { l.tail.next = s } l.tail = s goparkunlock(&l.lock, waitReasonSyncCondWait, traceEvGoBlockCond, 3) releaseSudog(s) }
除了將當(dāng)前Goroutine
追加到鏈表末端外,我們還會調(diào)用runtime.goparkunlock
令當(dāng)前Goroutine
陷入休眠。該函數(shù)也是在Go語言切換Goroutine
時常用的方法,它會直接讓出當(dāng)前處理器的使用權(quán)并等待調(diào)度器喚醒。
sync.Cond.Signal
和sync.Cond.Broadcast
方法就是用來喚醒陷入休眠的Goroutine
的,它們的實現(xiàn)有一些細微差別:
sync.Cond.Signal
方法會喚醒隊列最前面的Goroutine
sync.Cond.Broadcast
方法會喚醒隊列中全部Goroutine
func (c *Cond) Signal() { c.checker.check() runtime_notifyListNotifyOne(&c.notify) } func (c *Cond) Broadcast() { c.checker.check() runtime_notifyListNotifyAll(&c.notify) }
runtime.notifyListNotifyOne
只會從sync.notifyList
鏈表中找到滿足sudog.ticket == l.notify
條件的Goroutine
,并通過runtime.readyWithTime
將其喚醒:
func notifyListNotifyOne(l *notifyList) { t := l.notify atomic.Store(&l.notify, t + 1) for p, s := (*sudog)(nil), l.head; s != nil; p, s = s, s.next { if s.tiket == t { n := s.next if p != nil { p.next = n } else { l.head = n } if n == nil { l.tail = p } s.next = nil readyWithTime(s, 4) return } } }
runtime.notifyListNotifyAll
會依次通過runtime.readyWithTime
喚醒鏈表中的Goroutine
:
func notifyListNotifyAll(l *notifyList) { s := l.head l.head = nil l.tail = nil atomic.Store(&l.notify, atomic.Load(&l.wait)) for s != nil { next := s.next s.next = nil readyWithTime(s, 4) s = next } }
Goroutine
的喚醒順序也是按照加入隊列的先后順序,先加入的會先被喚醒,而后加入的Goroutine
可能需要等待調(diào)度器的調(diào)度。
一般情況下,我們會先調(diào)用sync.Cond.Wait
陷入休眠等待滿足期望條件,當(dāng)滿足期望條件時,就可以選用sync.Cond.Signal
或者sync.Cond.Broadcast
喚醒一個或者全部Goroutine
。
小結(jié)
sync.Cond
不是常用的同步機制,但是在條件長時間無法滿足時,與使用for {}
進行忙碌等待相比,sync.Cond
能夠讓出處理器的使用權(quán),提高CPU
的利用率。
使用時需要注意以下問題:
sync.Cond.Wait
在調(diào)用之前一定要先獲取互斥鎖,否則會觸發(fā)程序崩潰sync.Cond.Signal
喚醒的Goroutine
都是隊列最前面、等待最久的Goroutine
sync.Cond.Broadcast
會按照一定順序廣播通知等待的全部Goroutine
到此這篇關(guān)于Go語言sync.Cond使用方法詳解的文章就介紹到這了,更多相關(guān)Go語言sync.Cond內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go?module化?import?調(diào)用本地模塊?tidy的方法
這篇文章主要介紹了go?module化?import?調(diào)用本地模塊?tidy的相關(guān)知識,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-09-09go語言實現(xiàn)一個簡單的http客戶端抓取遠程url的方法
這篇文章主要介紹了go語言實現(xiàn)一個簡單的http客戶端抓取遠程url的方法,實例分析了Go語言http操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-03-03Golang 使用Map實現(xiàn)去重與set的功能操作
這篇文章主要介紹了Golang 使用 Map 實現(xiàn)去重與 set 的功能操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-04-04