1. 用法详解
sync.Cond 用于等待任务的唤醒,以跑步比赛为例,每一个运动员都是等待线程,sync.Cond对象是裁判员,所有等待运行的线程只有收到裁判员的信号后才会继续运行,其具体实现如下所示。
package main
import (
"fmt"
"sync"
"time"
)
func main() {
//3个人赛跑,1个裁判员发号施令
cond := sync.NewCond(&sync.Mutex{})
var wg sync.WaitGroup
wg.Add(4) //3选手+1裁判
for i := 1; i <= 3; i++ {
go func(num int) {
defer wg.Done()
fmt.Println(num, "号选手已经就位")
cond.L.Lock()
cond.Wait() //等待发令枪响
fmt.Println(num, "号选手开始跑……")
cond.L.Unlock()
}(i)
}
//等待所有goroutine都进入wait状态
time.Sleep(2 * time.Second)
go func() {
defer wg.Done()
fmt.Println("裁判:“各就各位~~预备~~”")
fmt.Println("啪!!!")
cond.Broadcast() //发令枪响
}()
//防止函数提前返回退出
wg.Wait()
}
运行结果:
3 号选手已经就位
1 号选手已经就位
2 号选手已经就位
裁判:“各就各位~~预备~~”
啪!!!
2 号选手开始跑……
3 号选手开始跑……
1 号选手开始跑……
2. 方法与源码详解
Cond 源码
type Cond struct {
noCopy noCopy
// L is held while observing or changing the condition
L Locker
notify notifyList
checker copyChecker
}
nocopy:是在go vet工具检验时,表明 WaitGroup 结构体的具体实现是不可被复制的。checker:双重检查(Double check)防止运行时拷贝L:可以传入一个读写锁或互斥锁,当修改条件或者调用wait方法时需要加锁notify:通知链表,调用wait()方法的Goroutine会放到这个链表中,唤醒从这里取。
copyChecker 源码
copyChecker 对比了指针本身所在的地址与指针指向的地址,来判断是否发生了拷贝。
// copyChecker holds back pointer to itself to detect object copying.
type copyChecker uintptr
func (c *copyChecker) check() {
if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
!atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
uintptr(*c) != uintptr(unsafe.Pointer(c)) {
panic("sync.Cond is copied")
}
}
notifyList 源码
type notifyList struct {
wait uint32
notify uint32
lock uintptr // key field of the mutex
head unsafe.Pointer
tail unsafe.Pointer
}
wait:下一个等待唤醒Goroutine的索引,他是在锁外自动递增的.notify:下一个要通知的Goroutine的索引,他可以在锁外读取,但是只能在锁持有的情况下写入.head:指向链表的头部tail:指向链表的尾部
wait() 源码
func (c *Cond) Wait() {
c.checker.check()
t := runtime_notifyListAdd(&c.notify)
c.L.Unlock()
runtime_notifyListWait(&c.notify, t)
c.L.Lock()
}
- 执行运行期间拷贝检查,如果发生了拷贝,则直接
panic程序 - 调用
runtime_notifyListAdd将等待计数器加一并解锁; - 调用
runtime_notifyListWait等待其他Goroutine的唤醒并加锁
Signal() 与 Broadcast() 源码
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)
}
本文正在参加技术专题18期-聊聊Go语言框架