一.sync.Once
Once
(單次執行)
用途:確保某個操作只執行一次(如初始化配置)
核心方法:Do(f func())
:保證 f
只執行一次
package mainimport ("fmt""sync"
)var (config map[string]stringonce sync.Oncewg 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 結束時減少計數器loadConfig()}()}wg.Wait() // 等待所有 goroutine 完成
}
可以確保這個協程只進行一次
二.sync.Cond
sync.Cond 是 golang 標準庫提供的并發協調器,用于支援開放人員在指定條件下阻塞和喚醒協程的操作.
Wait()
:釋放鎖并阻塞,直到被喚醒。Signal()
:喚醒一個等待的 goroutine。Broadcast()
:喚醒所有等待的 goroutine。
2.1 數據結構與構造器方法
type Cond struct {// 不可以對其進行值拷貝noCopy noCopy// 一個自旋鎖L Locker// 一個隊列,存放阻塞的goroutinenotify notifyListchecker copyChecker
}// NewCond returns a new Cond with Locker l.
func NewCond(l Locker) *Cond {return &Cond{L:l}
}
(1)成員變量 noCopy + checker 是一套組合拳,保證 Cond 在第一次使用后不允許被復制;
(2)核心變量 L,一把鎖,用于實現阻塞操作;
(3)核心變量 notify,阻塞鏈表,分別存儲了調用 Cond.Wait() 方法的次數、goroutine 被喚醒的次數、一把系統運行時的互斥鎖以及鏈表的頭尾節點.
type notifyList struct {wait uint32notify uint32lock uintptr // key field of the mutexhead unsafe.Pointertail unsafe.Pointer
}
2.2 Cond.Wait
作用:把當前這個持有鎖的goroutine,釋放鎖,陷入一個被動阻塞的狀態,加入阻塞隊列里面。
什么時候被喚醒呢?
當有其他的goroutine也持有這個Cond的引用,使用Signal函數的時候,會首先喚醒隊首的goroutine
通過這個機制就可以實現異步goroutine的協調,比如需要某一個goroutine實現了某一個動作,另外一個goroutine才可以繼續執行的一個場景。
使用的前置條件
看下面的代碼我們會發現,它有一個解鎖的操作,所以在調用他之前,必須是加鎖的狀態,只有這樣才可以執行,然后陷入被動阻塞的狀態,阻塞喚醒之后,才會重新加鎖。
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 統計數加 1;
(3)當前協程釋放鎖,因為接下來即將被操作系統 park;
(4)將當前協程包裝成節點,添加到 Cond 的阻塞隊列當中,并調用 park 操作將當前協程掛起;
(5)協程被喚醒后,重新嘗試獲取鎖.
2.3 Cond.Signal
作用:就是喚醒隊首的goroutine喚醒
func (c *Cond) Signal() {c.checker.check()runtime_notifyListNotifyOne(&c.notify)
}
(1)檢查 Cond 是否在首次使用后被拷貝,是則 panic;
(2)該 Cond 阻塞鏈表 notify 統計數加 1;
(3)從頭開始遍歷阻塞鏈表,喚醒一個等待時間最長的 goroutine.
2.4 Cond.BroadCast
作用:就是將阻塞隊列里面的所有goroutine都進行喚醒。
func (c *Cond) Broadcast() {c.checker.check()runtime_notifyListNotifyAll(&c.notify)
}
(1)檢查 Cond 是否在首次使用后被拷貝,是則 panic;
(2)取 wait 值賦值給 notify;
(3)喚醒阻塞鏈表所有節點.
2.5 使用案例
package mainimport ("fmt""sync""time"
)func main() {var mu sync.Mutexcond := sync.NewCond(&mu)// 共享狀態isReady := false// 等待條件的goroutinego func() {fmt.Println("等待者: 等待條件滿足...")cond.L.Lock()defer cond.L.Unlock()// 使用循環防止虛假喚醒for !isReady {cond.Wait() // 釋放鎖并阻塞,喚醒時會重新獲得鎖fmt.Println("等待者: 被喚醒,檢查條件")}fmt.Println("等待者: 條件已滿足!")}()// 改變條件的goroutinego func() {time.Sleep(2 * time.Second) // 模擬耗時操作fmt.Println("觸發者: 準備改變條件...")cond.L.Lock()isReady = truecond.L.Unlock()fmt.Println("觸發者: 發送通知")cond.Signal() // 喚醒一個等待的goroutine}()time.Sleep(3 * time.Second) // 等待所有goroutine完成
}