Go語言sync.Once和sync.Cond的實現(xiàn)
一.sync.Once
Once
(單次執(zhí)行)
用途:確保某個操作只執(zhí)行一次(如初始化配置)
核心方法:Do(f func())
:保證 f
只執(zhí)行一次
package main import ( "fmt" "sync" ) var ( config map[string]string once sync.Once wg sync.WaitGroup ) func loadConfig() { once.Do(func() { fmt.Println("Loading config...") config = map[string]string{"key": "value"} }) } func main() { for i := 0; i < 5; i++ { wg.Add(1) go func() { defer wg.Done() // 確保 goroutine 結(jié)束時減少計數(shù)器 loadConfig() }() } wg.Wait() // 等待所有 goroutine 完成 }
可以確保這個協(xié)程只進行一次
二.sync.Cond
sync.Cond 是 golang 標(biāo)準(zhǔn)庫提供的并發(fā)協(xié)調(diào)器,用于支援開放人員在指定條件下阻塞和喚醒協(xié)程的操作.
Wait()
:釋放鎖并阻塞,直到被喚醒。Signal()
:喚醒一個等待的 goroutine。Broadcast()
:喚醒所有等待的 goroutine。
2.1 數(shù)據(jù)結(jié)構(gòu)與構(gòu)造器方法
type Cond struct { // 不可以對其進行值拷貝 noCopy noCopy // 一個自旋鎖 L Locker // 一個隊列,存放阻塞的goroutine notify notifyList checker copyChecker } // NewCond returns a new Cond with Locker l. func NewCond(l Locker) *Cond { return &Cond{L:l} }
(1)成員變量 noCopy + checker 是一套組合拳,保證 Cond 在第一次使用后不允許被復(fù)制;
(2)核心變量 L,一把鎖,用于實現(xiàn)阻塞操作;
(3)核心變量 notify,阻塞鏈表,分別存儲了調(diào)用 Cond.Wait() 方法的次數(shù)、goroutine 被喚醒的次數(shù)、一把系統(tǒng)運行時的互斥鎖以及鏈表的頭尾節(jié)點.
type notifyList struct { wait uint32 notify uint32 lock uintptr // key field of the mutex head unsafe.Pointer tail unsafe.Pointer }
2.2 Cond.Wait
作用:把當(dāng)前這個持有鎖的goroutine,釋放鎖,陷入一個被動阻塞的狀態(tài),加入阻塞隊列里面。
什么時候被喚醒呢?
當(dāng)有其他的goroutine也持有這個Cond的引用,使用Signal函數(shù)的時候,會首先喚醒隊首的goroutine
通過這個機制就可以實現(xiàn)異步goroutine的協(xié)調(diào),比如需要某一個goroutine實現(xiàn)了某一個動作,另外一個goroutine才可以繼續(xù)執(zhí)行的一個場景。
使用的前置條件
看下面的代碼我們會發(fā)現(xiàn),它有一個解鎖的操作,所以在調(diào)用他之前,必須是加鎖的狀態(tài),只有這樣才可以執(zhí)行,然后陷入被動阻塞的狀態(tài),阻塞喚醒之后,才會重新加鎖。
func (c *Cond) Wait() { c.checker.check() t := runtime_notifyListAdd(&c.notify) c.L.Unlock() runtime_notifyListWait(&c.notify, t) c.L.Lock() }
(1)檢查 Cond 是否在使用過后被拷貝,是則 panic;
(2)該 Cond 阻塞鏈表 wait 統(tǒng)計數(shù)加 1;
(3)當(dāng)前協(xié)程釋放鎖,因為接下來即將被 操作系統(tǒng) park;
(4)將當(dāng)前協(xié)程包裝成節(jié)點,添加到 Cond 的阻塞隊列當(dāng)中,并調(diào)用 park 操作將當(dāng)前協(xié)程掛起;
(5)協(xié)程被喚醒后,重新嘗試獲取鎖.
2.3 Cond.Signal
作用:就是喚醒隊首的goroutine喚醒
func (c *Cond) Signal() { c.checker.check() runtime_notifyListNotifyOne(&c.notify) }
(1)檢查 Cond 是否在首次使用后被拷貝,是則 panic;
(2)該 Cond 阻塞鏈表 notify 統(tǒng)計數(shù)加 1;
(3)從頭開始遍歷阻塞鏈表,喚醒一個等待時間最長的 goroutine.
2.4 Cond.BroadCast
作用:就是將阻塞隊列里面的所有g(shù)oroutine都進行喚醒。
func (c *Cond) Broadcast() { c.checker.check() runtime_notifyListNotifyAll(&c.notify) }
(1)檢查 Cond 是否在首次使用后被拷貝,是則 panic;
(2)取 wait 值賦值給 notify;
(3)喚醒阻塞鏈表所有節(jié)點.
2.5 使用案例
package main import ( "fmt" "sync" "time" ) func main() { var mu sync.Mutex cond := sync.NewCond(&mu) // 共享狀態(tài) isReady := false // 等待條件的goroutine go func() { fmt.Println("等待者: 等待條件滿足...") cond.L.Lock() defer cond.L.Unlock() // 使用循環(huán)防止虛假喚醒 for !isReady { cond.Wait() // 釋放鎖并阻塞,喚醒時會重新獲得鎖 fmt.Println("等待者: 被喚醒,檢查條件") } fmt.Println("等待者: 條件已滿足!") }() // 改變條件的goroutine go func() { time.Sleep(2 * time.Second) // 模擬耗時操作 fmt.Println("觸發(fā)者: 準(zhǔn)備改變條件...") cond.L.Lock() isReady = true cond.L.Unlock() fmt.Println("觸發(fā)者: 發(fā)送通知") cond.Signal() // 喚醒一個等待的goroutine }() time.Sleep(3 * time.Second) // 等待所有g(shù)oroutine完成 }
到此這篇關(guān)于Go語言sync.Once和sync.Cond的實現(xiàn)的文章就介紹到這了,更多相關(guān)Go語言sync.Once和sync.Cond內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Goland使用delve進行遠程調(diào)試的詳細教程
網(wǎng)上給出的使用delve進行遠程調(diào)試,都需要先在本地交叉編譯或者在遠程主機上編譯出可運行的程序,然后再用delve在遠程啟動程序,本教程會將上面的步驟簡化為只需要兩步,1,在遠程運行程序2,在本地啟動調(diào)試,需要的朋友可以參考下2024-08-08golang中的select關(guān)鍵字用法總結(jié)
這篇文章主要介紹了golang中的select關(guān)鍵字用法總結(jié),本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06