場景:
生產者消費者模型中(比如一個有緩存的channel中),生產者進行生產之前去搶鎖,如果緩存已滿,生產者搶鎖無效;消費者進行消費之前去搶鎖,如果緩存爲空,消費者搶鎖無效。所有在搶鎖之前需要進行一個判斷,稱之爲條件變量。
sync.Cond的結構:
type Cond struct {
noCopy noCopy
// L is held while observing or changing the condition
L Locker
notify notifyList
checker copyChecker
}
其中有一把鎖。
sync.Cond結構體實現的方法:
Wait() // 阻塞-加鎖-解鎖
Wait()一旦阻塞後,就需要喚醒才能進行搶鎖,即需要Signal()或Broadcast()進行喚醒。
func (c *Cond) Wait() {
c.checker.check()
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}
Signal() // 喚醒一個goroutine
func (c *Cond) Signal() {
c.checker.check()
runtime_notifyListNotifyOne(&c.notify)
}
Broadcast() // 喚醒所有goroutine
func (c *Cond) Broadcast() {
c.checker.check()
runtime_notifyListNotifyAll(&c.notify)
}
實際用例
構建一個緩存爲3的channel,分別用5個生產者消費者去寫讀數據。
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var cond sync.Cond
func producer(in chan<- int) {
for {
cond.L.Lock()
for len(in) == 3 { // 循環判斷緩存是否已滿。
// 這裏使用for循環而不是if判斷的原因:因爲這時緩存已經滿了,
// 再喚醒生產者還是會進入wait的狀態,這裏想喚醒consumer
// 進行消費一個緩存,然後才能繼續進行下去
cond.Signal() // 喚醒之前一個goroutine
cond.Wait() // 阻塞-解鎖-加鎖
}
num := rand.Intn(100) // 寫入100以內隨機的數字
in <- num
fmt.Println("生產了", num)
cond.L.Unlock()
}
}
func consumer(out <-chan int) {
for {
cond.L.Lock()
for len(out) == 0 { // 循環判斷緩存是否爲空
cond.Signal()
cond.Wait()
}
num := <- out
fmt.Println("消費了", num)
cond.L.Unlock()
}
}
func main() {
ch := make(chan int, 3)
cond.L = new(sync.Mutex) // 需要重新定義鎖
rand.Seed(time.Now().UnixNano()) // 爲隨機數添加隨機種子
for i := 0; i < 5; i++ {
go producer(ch)
}
for i := 0; i < 5; i++ {
go consumer(ch)
}
select {} // 阻塞主函數
}
Output:
生產了 22
生產了 82
生產了 33
消費了 22
消費了 82
消費了 33
生產了 81
生產了 86
......