Cond并发原语学习
Java 面试中,经常被问到的一个知识点就是等待 / 通知(wait/notify)机制。面试官经常
会这样考察候选人:请实现一个限定容量的队列(queue),当队列满或者空的时候,利
用等待 / 通知机制实现阻塞或者唤醒。
Go 标准库提供 Cond 原语的目的是,为等待 / 通知场景下的并发问题提供支持。Cond 通
常应用于等待某个条件的一组 goroutine,等条件变为 true 的时候,其中一个 goroutine
或者所有的 goroutine 都会被唤醒执行。
从开发实践上,我们真正使用 Cond 的场景比较少,因为一旦遇到需要使用 Cond 的场
景,我们更多地会使用 Channel 的方式 ,因为那才是更地道的 Go 语言的写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
func main() {
c := sync.NewCond(&sync.Mutex{})
var ready int
for i := 0; i < 10; i++ {
go func(i int) {
time.Sleep(time.Duration(rand.Int63n(10)) * time.Second)
// 加锁更改等待条件
c.L.Lock()
ready++
c.L.Unlock()
log.Printf("运动员#%d 已准备就绪\n", i)
// 广播唤醒所有的等待者
c.Broadcast()
}(i)
}
c.L.Lock()
for ready != 10 {
c.Wait()
log.Println("裁判员被唤醒一次")
}
c.L.Unlock()
//所有的运动员是否就绪
log.Println("所有运动员都准备就绪。比赛开始,3,2,1, ......")
}
|
Cond 的实现原理
其实,Cond 的实现非常简单,或者说复杂的逻辑已经被 Locker 或者 runtime 的等待队
列实现了。我们直接看看 Cond 的源码吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
type Cond struct {
noCopy noCopy
// 当观察或者修改等待条件的时候需要加锁
L Locker
// 等待队列
notify notifyList
checker copyChecker
}
func NewCond(l Locker) *Cond {
return &Cond{L: l}
}
func (c *Cond) Wait() {
c.checker.check()
// 增加到等待队列中
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
// 阻塞休眠直到被唤醒
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}
func (c *Cond) Signal() {
c.checker.check()
runtime_notifyListNotifyOne(&c.notify)
}
func (c *Cond) Broadcast() {
c.checker.check()
runtime_notifyListNotifyAll(&c.notify)
}
|
第一,同样的场景我们会使用其他的并发原语来替代。Go 特有的 Channel 类型,有一个
应用很广泛的模式就是通知机制,这个模式使用起来也特别简单。所以很多情况下,我们
会使用 Channel 而不是 Cond 实现 wait/notify 机制。
第二,对于简单的 wait/notify 场景,比如等待一组 goroutine 完成之后继续执行余下的
代码,我们会使用 WaitGroup 来实现。因为 WaitGroup 的使用方法更简单,而且不容易
出错。比如,上面百米赛跑的问题,就可以很方便地使用 WaitGroup 来实现。