Go語(yǔ)言sync.Cond使用方法詳解
概述
每一個(gè)sync.Cond結(jié)構(gòu)體在初始化時(shí)都需要傳入一個(gè)互斥鎖,我們可以通過(guò)下面的例子了解它的使用方法:
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()
}運(yùn)行結(jié)果:
listen
...
listen
上述代碼同時(shí)運(yùn)行了 11 個(gè)Goroutine,它們分別做了不同事情:
- 10個(gè)
Goroutine通過(guò)sync.Cond.Wait等待特定條件滿足 - 1個(gè)
Goroutine會(huì)調(diào)用sync.Cond.Broadcast喚醒所有陷入等待的Goroutine
調(diào)用sync.Cond.Broadcast方法后,上述代碼會(huì)打印出10次 "listen" 并結(jié)束調(diào)用。

結(jié)構(gòu)體
sync.Cond的結(jié)構(gòu)體中包含以下 4 個(gè)字段:
type Cond struct {
noCopy noCopy
L Locker
notify notifyList
checker copyChecker
}- noCopy —— 用于保證結(jié)構(gòu)體不會(huì)在編譯期間復(fù)制
- L —— 用于保護(hù)內(nèi)部的
notify字段,Locker接口類型的變量 - notify —— 一個(gè)
Goroutine的鏈表,它是實(shí)現(xiàn)同步機(jī)制的核心結(jié)構(gòu) - copyChecker —— 用于禁止運(yùn)行期間發(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對(duì)外暴露的sync.Cond.Wait方法會(huì)令當(dāng)前Goroutine陷入休眠狀態(tài),它的執(zhí)行過(guò)程分成以下兩個(gè)步驟:
- 調(diào)用
runtime.notifyListAdd將等待計(jì)時(shí)器加一并解鎖 - 調(diào)用
runtime.notifyListWait等待其他Goroutine被喚醒并對(duì)其加鎖
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 會(huì)獲取當(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追加到鏈表末端外,我們還會(huì)調(diào)用runtime.goparkunlock令當(dāng)前Goroutine陷入休眠。該函數(shù)也是在Go語(yǔ)言切換Goroutine時(shí)常用的方法,它會(huì)直接讓出當(dāng)前處理器的使用權(quán)并等待調(diào)度器喚醒。

sync.Cond.Signal和sync.Cond.Broadcast方法就是用來(lái)喚醒陷入休眠的Goroutine的,它們的實(shí)現(xiàn)有一些細(xì)微差別:
sync.Cond.Signal方法會(huì)喚醒隊(duì)列最前面的Goroutinesync.Cond.Broadcast方法會(huì)喚醒隊(duì)列中全部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只會(huì)從sync.notifyList鏈表中找到滿足sudog.ticket == l.notify條件的Goroutine,并通過(guò)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會(huì)依次通過(guò)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的喚醒順序也是按照加入隊(duì)列的先后順序,先加入的會(huì)先被喚醒,而后加入的Goroutine可能需要等待調(diào)度器的調(diào)度。
一般情況下,我們會(huì)先調(diào)用sync.Cond.Wait陷入休眠等待滿足期望條件,當(dāng)滿足期望條件時(shí),就可以選用sync.Cond.Signal或者sync.Cond.Broadcast喚醒一個(gè)或者全部Goroutine。
小結(jié)
sync.Cond不是常用的同步機(jī)制,但是在條件長(zhǎng)時(shí)間無(wú)法滿足時(shí),與使用for {}進(jìn)行忙碌等待相比,sync.Cond能夠讓出處理器的使用權(quán),提高CPU的利用率。
使用時(shí)需要注意以下問(wèn)題:
sync.Cond.Wait在調(diào)用之前一定要先獲取互斥鎖,否則會(huì)觸發(fā)程序崩潰sync.Cond.Signal喚醒的Goroutine都是隊(duì)列最前面、等待最久的Goroutinesync.Cond.Broadcast會(huì)按照一定順序廣播通知等待的全部Goroutine
到此這篇關(guān)于Go語(yǔ)言sync.Cond使用方法詳解的文章就介紹到這了,更多相關(guān)Go語(yǔ)言sync.Cond內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go?module化?import?調(diào)用本地模塊?tidy的方法
這篇文章主要介紹了go?module化?import?調(diào)用本地模塊?tidy的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-09-09
Go語(yǔ)言使用組合的方式實(shí)現(xiàn)多繼承的方法
這篇文章主要介紹了Go語(yǔ)言使用組合的方式實(shí)現(xiàn)多繼承的方法,實(shí)例分析了多繼承的原理與使用組合方式來(lái)實(shí)現(xiàn)多繼承的技巧,需要的朋友可以參考下2015-02-02
go語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的http客戶端抓取遠(yuǎn)程url的方法
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)一個(gè)簡(jiǎn)單的http客戶端抓取遠(yuǎn)程url的方法,實(shí)例分析了Go語(yǔ)言http操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
go-micro微服務(wù)JWT跨域認(rèn)證問(wèn)題
JWT 以 JSON 對(duì)象的形式安全傳遞信息。因?yàn)榇嬖跀?shù)字簽名,因此所傳遞的信息是安全的,這篇文章主要介紹了go-micro微服務(wù)JWT跨域認(rèn)證,需要的朋友可以參考下2023-01-01
Golang 使用Map實(shí)現(xiàn)去重與set的功能操作
這篇文章主要介紹了Golang 使用 Map 實(shí)現(xiàn)去重與 set 的功能操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
Go語(yǔ)言實(shí)現(xiàn)逐行讀取和寫入文件詳解
這篇文章主要介紹了如何使用go語(yǔ)言實(shí)現(xiàn)從輸入文件中讀取每行數(shù)據(jù),然后將每行字段組合成SQL插入腳本,然后逐行寫入另外一個(gè)空白文件中,有需要的可以參考下2024-01-01

