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() }
運(yùn)行結(jié)果:
listen
...
listen
上述代碼同時運(yùn)行了 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 —— 用于保護(hù)內(nèi)部的
notify
字段,Locker
接口類型的變量 - notify —— 一個
Goroutine
的鏈表,它是實現(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
對外暴露的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)有一些細(xì)微差別:
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
不是常用的同步機(jī)制,但是在條件長時間無法滿足時,與使用for {}
進(jìn)行忙碌等待相比,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)文章
Golang中由零值和gob庫特性引起B(yǎng)UG解析
這篇文章主要為大家介紹了Golang中由零值和gob庫特性引起B(yǎng)UG解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04golang+vue打造高效多語言博客系統(tǒng)的完整指南
這篇文章主要為大家詳細(xì)介紹了如何使用golang和vue打造一個高效多語言博客系統(tǒng),本文為大家附上了完整版指南,有需要的小伙伴可以參考一下2025-03-03GO 使用Webhook 實現(xiàn)github 自動化部署的方法
這篇文章主要介紹了GO 使用Webhook 實現(xiàn)github 自動化部署的方法,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05Golang中函數(shù)(Function)和方法(Method)的區(qū)別詳解
在Golang中,大家必然會頻繁使用到函數(shù)(Function)和方法(Method),但是有的同學(xué)可能并沒有注意過函數(shù)和方法的異同點,函數(shù)和方法都是用來執(zhí)行特定任務(wù)的代碼塊,雖然很相似,但也有很大的區(qū)別,所以本文將詳細(xì)講解函數(shù)和方法的定義以及它們的異同點2023-07-07Go語言讀取,設(shè)置Cookie及設(shè)置cookie過期方法詳解
這篇文章主要介紹了Go語言讀取,設(shè)置Cookie及設(shè)置cookie過期方法詳解,需要的朋友可以參考下2022-04-04go語言reflect.Type?和?reflect.Value?應(yīng)用示例詳解
這篇文章主要為大家介紹了go語言reflect.Type?和?reflect.Value?應(yīng)用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09