最近在學習Go 語言高性能編程時看到了sync.Cond
條件變量這個概念,一時有些難以理解。查閱資料後對其有了些認知,特此記錄。
概念#
sync
包提供了條件變量類型sync.Cond
,它可以和互斥鎖或讀寫鎖組合使用,用來協調想要訪問共享變量的協程。其主要作用機制是在對應的共享資源狀態發生變化時,通知其它因此而阻塞的協程。
定義#
sync.Cond
是一個結構體,其定義如下:
// Each Cond has an associated Locker L (often a *Mutex or *RWMutex),
// which must be held when changing the condition and
// when calling the Wait method.
//
// A Cond must not be copied after first use.
type Cond struct {
noCopy noCopy
// L is held while observing or changing the condition
L Locker
notify notifyList
checker copyChecker
}
該類型可通過sync
包中的func NewCond(l Locker) *Cond
方法創建。
它有如下三個方法:
-
func (c *Cond) Broadcast()
喚醒所有等待 c 的協程。
-
func (c *Cond) Signal()
喚醒單個等待 c 的協程(如果有的話)。
-
func (c *Cond) Wait()
自動解鎖
c.L
並暫停該協程執行,直到被喚醒。該函數在返回前會重新對c.L
加鎖。
應用場景#
由以上描述可知,該結構的主要用法應該是多個協程調用Wait
等待某個協程運行,該協程任務完畢後調用Broadcast
或Signal
將等待的協程喚醒。即:多用在多個協程等待,一個協程通知的場景。
舉個生活中的小例子:
多位同學到達食堂,此時食堂還未做完飯,同學們需要等待食堂出餐後才能恰飯。
用Go
條件變量對以上場景進行描述,可寫出如下代碼:
package main
import (
"fmt"
"sync"
"time"
)
var (
ok = false // 是否做完飯
food = "" // 飯的名字
rwMutex = &sync.RWMutex{} // 讀寫鎖,用於鎖定飯名的修改
cond = sync.NewCond(rwMutex.RLocker()) // 條件變量使用讀寫鎖中的讀鎖
)
func makeFood() {
// 做飯使用寫鎖(當然因為只有一個做飯協程,該鎖並無實際意義)
rwMutex.Lock()
defer rwMutex.Unlock()
fmt.Println("食堂開始做飯!")
time.Sleep(3 * time.Second)
ok = true
food = "魚香肉絲"
fmt.Println("食堂做完飯了!")
cond.Broadcast()
}
func waitToEat() {
cond.L.Lock()
defer cond.L.Unlock()
for !ok {
cond.Wait()
}
fmt.Println("總算吃到飯了,這頓吃的是", food)
}
func main() {
for i := 0; i < 3; i++ {
go waitToEat()
}
go makeFood()
time.Sleep(4 * time.Second)
}
運行該程序,結果如下:
食堂開始做飯!
食堂做完飯了!
總算吃到飯了,這頓吃的是 魚香肉絲
總算吃到飯了,這頓吃的是 魚香肉絲
總算吃到飯了,這頓吃的是 魚香肉絲
與其他概念的區別#
channel#
眾所周知,channel
同樣是 Go 中用於協程同步的概念,但與sync.Cond
不同,channel
多用於一對一通信,如果繼承上面的例子,這次的場景應該是:
一位同學到達食堂,此時食堂還未做完飯,該同學需要等待食堂出餐後才能恰飯。
用channel
描述為:
package main
import (
"fmt"
"time"
)
var (
food = ""
ch = make(chan struct{})
)
func makeFood() {
fmt.Println("食堂開始做飯!")
time.Sleep(3 * time.Second)
food = "魚香肉絲"
fmt.Println("食堂做完飯了!")
ch <- struct{}{}
}
func waitToEat() {
<-ch
fmt.Println("總算吃到飯了,這頓吃的是", food)
}
func main() {
go waitToEat()
go makeFood()
time.Sleep(4 * time.Second)
}
運行結果為:
食堂開始做飯!
食堂做完飯了!
總算吃到飯了,這頓吃的是 魚香肉絲
sync.WaitGroup#
sync.WaitGroup
同樣用於協程同步,但應用場景與sync.Cond
剛好相反,後者多用於多協程等待,單協程通知,而前者多用於單協程等待多協程執行完畢。
仍然使用上述例子:
一位同學到達食堂,此時食堂多個窗口均未做完飯,該同學想要等待每個窗口都做完飯後各點一份吃。(多少有點離譜了 XD)
代碼描述如下:
package main
import (
"fmt"
"sync"
"time"
)
var (
wg sync.WaitGroup
)
func makeFood() {
fmt.Println("窗口開始做飯!")
time.Sleep(3 * time.Second)
fmt.Println("窗口做完飯了!")
wg.Done()
}
func waitToEat() {
wg.Wait()
fmt.Println("oho,每個窗口來一份!")
}
func main() {
for i := 0; i < 3; i++ {
wg.Add(1)
go makeFood()
}
go waitToEat()
time.Sleep(4 * time.Second)
}
運行結果:
窗口開始做飯!
窗口開始做飯!
窗口開始做飯!
窗口做完飯了!
窗口做完飯了!
窗口做完飯了!
oho,每個窗口來一份!
結語#
以上為個人的淺薄理解,如有錯誤歡迎在評論區指出,感謝!