如果有一个任务需要满足一定条件才可以执行。一般都会想到使用channel可以实现,但是channel的方式比较适用于一对一的方式,一对多的话并不是很合适,这时候就可以使用sync.Cond,它可以实现多个协程间的通知。
sync.Cond值有一个sync.Locker类型的命名为L的字段,L具体值通常为*sync.Mutex或者*sync.RWMutex。
1
2
3
4
5
6
7
8
9
10
11
12
13
type Cond struct {
noCopy noCopy
// L is held while observing or changing the condition
L Locker
notify notifyList
checker copyChecker
}
// NewCond returns a new Cond with Locker l.
func NewCond(l Locker) *Cond {
return &Cond{L: l}
}
*sync.Cond类型有三个方法:Wait(), Signal()、Broadcast()。
每个Cond值维护着一个先进先出等待协程队列。
c.Wait()
1
2
3
4
5
6
7
func (c *Cond) Wait() {
c.checker.check()
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}
通过源码可以看到一个c.Wait()调用将:
- 首先将当前协程推入到c所维护的等待协程队列。
- 然后调用c.L.Unlock()对c.L解锁。
- 然后使当前协程进入阻塞状态。(另一个协程通过调用c.Signal()或c.Broadcast()唤醒而重新进入运行状态)。
一旦当前协程重新进入运行状态,c.L.Lock()将被调用以试图重新进行加锁,调用成功后c.Wait()退出。
c.Signal()
1
2
3
4
func (c *Cond) Signal() {
c.checker.check()
runtime_notifyListNotifyOne(&c.notify)
}
调用c.Signal()将唤醒并移除c所维护的等待协程队列的第一个协程。
c.Broadcast()
1
2
3
4
func (c *Cond) Broadcast() {
c.checker.check()
runtime_notifyListNotifyAll(&c.notify)
}
调用c.Broadcast()将唤醒冰移除c所维护的等待协程队列中的所有协程。
一般情况下,c.Signal()和c.Broadcast()调用通常用来通知某个条件的状态发生了变化。c.Wait()应该在一个检查某个条件是否已经满足的循环中调用。
注意:c.Wait()必须在c.L字段值处于加锁状态的时候调用;否则会panic。而c.Signal()和c.Broadcast()不必在c.L处于加锁状态时调用。
示例
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
29
30
31
32
33
34
35
36
37
38
39
runtime.GOMAXPROCS(1)
cond := sync.NewCond(&sync.Mutex{})
for i := 0; i < 10; i++ {
go func(x int) { //worker
cond.L.Lock()
defer cond.L.Unlock()
cond.Wait()
fmt.Println(x)
}(i)
}
time.Sleep(time.Second)
fmt.Println("call Signal")
cond.Signal() //发送一个通知给已经获取锁的goroutine
time.Sleep(time.Second)
fmt.Println("call Signal")
cond.Signal()
time.Sleep(time.Second)
fmt.Println("call Signal")
cond.Signal()
time.Sleep(time.Second)
fmt.Println("call Broadcast")
cond.Broadcast() //广播给所有等待的goroutine
time.Sleep(time.Second)
// Output:
// call Signal
// 9
// call Signal
// 0
// call Signal
// 1
// call Broadcast
// 8
// 2
// 3
// 4
// 5
// 6
// 7