场景:
生产者消费者模型中(比如一个有缓存的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
......