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
等待特定條件滿(mǎn)足 - 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
接口類(lèi)型的變量 - 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ì)列最前面的Goroutine
sync.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
鏈表中找到滿(mǎn)足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
陷入休眠等待滿(mǎn)足期望條件,當(dāng)滿(mǎn)足期望條件時(shí),就可以選用sync.Cond.Signal
或者sync.Cond.Broadcast
喚醒一個(gè)或者全部Goroutine
。
小結(jié)
sync.Cond
不是常用的同步機(jī)制,但是在條件長(zhǎng)時(shí)間無(wú)法滿(mǎn)足時(shí),與使用for {}
進(jìn)行忙碌等待相比,sync.Cond
能夠讓出處理器的使用權(quán),提高CPU
的利用率。
使用時(shí)需要注意以下問(wèn)題:
sync.Cond.Wait
在調(diào)用之前一定要先獲取互斥鎖,否則會(huì)觸發(fā)程序崩潰sync.Cond.Signal
喚醒的Goroutine
都是隊(duì)列最前面、等待最久的Goroutine
sync.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)文章
一文詳解golang延時(shí)任務(wù)的實(shí)現(xiàn)
這篇文章主要為大家介紹了golang延時(shí)任務(wù)的實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03使用Golang編寫(xiě)一個(gè)簡(jiǎn)單的命令行工具
Cobra是一個(gè)強(qiáng)大的開(kāi)源工具,能夠幫助我們快速構(gòu)建出優(yōu)雅且功能豐富的命令行應(yīng)用,本文將利用Cobra編寫(xiě)一個(gè)簡(jiǎn)單的命令行工具,感興趣的可以了解下2023-12-12Golang中由零值和gob庫(kù)特性引起B(yǎng)UG解析
這篇文章主要為大家介紹了Golang中由零值和gob庫(kù)特性引起B(yǎng)UG解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04golang+vue打造高效多語(yǔ)言博客系統(tǒng)的完整指南
這篇文章主要為大家詳細(xì)介紹了如何使用golang和vue打造一個(gè)高效多語(yǔ)言博客系統(tǒng),本文為大家附上了完整版指南,有需要的小伙伴可以參考一下2025-03-03Go語(yǔ)言指針訪問(wèn)結(jié)構(gòu)體的方法
這篇文章主要介紹了Go語(yǔ)言指針訪問(wèn)結(jié)構(gòu)體的方法,涉及Go語(yǔ)言指針及結(jié)構(gòu)體的使用技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02GO 使用Webhook 實(shí)現(xiàn)github 自動(dòng)化部署的方法
這篇文章主要介紹了GO 使用Webhook 實(shí)現(xiàn)github 自動(dòng)化部署的方法,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05Golang中函數(shù)(Function)和方法(Method)的區(qū)別詳解
在Golang中,大家必然會(huì)頻繁使用到函數(shù)(Function)和方法(Method),但是有的同學(xué)可能并沒(méi)有注意過(guò)函數(shù)和方法的異同點(diǎn),函數(shù)和方法都是用來(lái)執(zhí)行特定任務(wù)的代碼塊,雖然很相似,但也有很大的區(qū)別,所以本文將詳細(xì)講解函數(shù)和方法的定義以及它們的異同點(diǎn)2023-07-07Go語(yǔ)言設(shè)置JSON的默認(rèn)值操作
這篇文章主要介紹了Go語(yǔ)言設(shè)置JSON的默認(rèn)值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12Go語(yǔ)言讀取,設(shè)置Cookie及設(shè)置cookie過(guò)期方法詳解
這篇文章主要介紹了Go語(yǔ)言讀取,設(shè)置Cookie及設(shè)置cookie過(guò)期方法詳解,需要的朋友可以參考下2022-04-04go語(yǔ)言reflect.Type?和?reflect.Value?應(yīng)用示例詳解
這篇文章主要為大家介紹了go語(yǔ)言reflect.Type?和?reflect.Value?應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09