07 | Cond:条件变量的实现机制及避坑指南
晁岳攀
该思维导图由 AI 生成,仅供参考
你好,我是鸟窝。
在写 Go 程序之前,我曾经写了 10 多年的 Java 程序,也面试过不少 Java 程序员。在 Java 面试中,经常被问到的一个知识点就是等待 / 通知(wait/notify)机制。面试官经常会这样考察候选人:请实现一个限定容量的队列(queue),当队列满或者空的时候,利用等待 / 通知机制实现阻塞或者唤醒。
在 Go 中,也可以实现一个类似的限定容量的队列,而且实现起来也比较简单,只要用条件变量(Cond)并发原语就可以。Cond 并发原语相对来说不是那么常用,但是在特定的场景使用会事半功倍,比如你需要在唤醒一个或者所有的等待者做一些检查操作的时候。
那么今天这一讲,我们就学习下 Cond 这个并发原语。
Go 标准库的 Cond
Go 标准库提供 Cond 原语的目的是,为等待 / 通知场景下的并发问题提供支持。Cond 通常应用于等待某个条件的一组 goroutine,等条件变为 true 的时候,其中一个 goroutine 或者所有的 goroutine 都会被唤醒执行。
顾名思义,Cond 是和某个条件相关,这个条件需要一组 goroutine 协作共同完成,在条件还没有满足的时候,所有等待这个条件的 goroutine 都会被阻塞住,只有这一组 goroutine 通过协作达到了这个条件,等待的 goroutine 才可能继续进行下去。
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
- 深入了解
- 翻译
- 解释
- 总结
Go标准库提供了Cond原语,用于支持等待/通知场景下的并发问题。Cond通常应用于等待某个条件的一组goroutine,等条件变为true时,其中一个或所有的goroutine都会被唤醒执行。文章介绍了Cond的基本用法,包括初始化、Broadcast、Signal和Wait方法的使用。此外,还探讨了Cond的实现原理,指出其实现逻辑相对简单,关键逻辑已被Locker或runtime的等待队列实现。文章还提到了使用Cond常见的两个错误,即调用Wait时未加锁和未检查条件是否满足就继续执行。文章深入浅出地介绍了Cond的使用方法和实现原理,对于想要了解并发编程的读者具有一定的参考价值。文章还提到了Cond在实际项目中的使用场景,以及与其他并发原语的比较,展示了Cond的独特特性和适用场景。文章最后提出了思考题,引发读者思考和讨论。整体而言,本文全面介绍了Cond的使用方法、实现原理和适用场景,对于并发编程感兴趣的读者具有一定的参考价值。
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》,新⼈⾸单¥59
《Go 并发编程实战课》,新⼈⾸单¥59
立即购买
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
登录 后留言
全部留言(31)
- 最新
- 精选
- 那时刻老师,上节课你提到noCopy,是一个辅助的、用来帮助 vet 检查用的类型,而Cond还有个copyChecker 是一个辅助结构,可以在运行时检查 Cond 是否被复制使用。 nocpoy是静态检查,copyChecker是运行时检查,不是理解是否正确? 另外不是是否有其他区别呢?
作者回复: 是的
2020-10-2613 - chongsheng关于Cond还有一种会不小心误用的场景,因为一些原因导致Wait执行的时候,Signal/Broadcast就已经执行完了,导致Wait一直等待无法唤醒。比如这里的例子 https://stackoverflow.com/questions/36857167/how-to-correctly-use-sync-cond
作者回复: 👍🏻
2022-01-062 - myrfy还是没有想明白k8s为什么不能用channel通知 close可以实现broadcast的功效,在pop的时候,也是只有一个goroutine可以拿到数据,感觉除了关闭队列之外,不存在需要broadcast的情况。也就是感觉不需要多次broadcast,这样channel应该是满足要求的……请老师明示
作者回复: 因为需要重用,使用chan close后没法重用了
2020-10-282 - moooofly请教一个问题,nocpoy 是用于 vet 静态检查,copyChecker 是为了运行时检查,都是为了检查 copy 问题,为啥 Cond 要在两处检查,而 Mutex 只需要一处?
作者回复: 你第一句给出了答案。分别应用不同阶段
2020-10-2721 - kyofunc 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, ......") } 这个例子有问题吧. 这里的 ready 变量共享了变量 c 的锁. 会导致在 c.Wait() 语句执行前 for 中的 goroutine 全部堵塞. 在 c.Wait() 前加句 time.Sleep(time.Second * 10) 试试就知道了. 是不是应该给 ready 变量单独定义一个 Mutex?
作者回复: wait会释放锁
2022-12-04归属地:北京 - Geek_b45293有个问题,为什么 Mutex 不使用 copyChecker 来检测是否被复制呢?
作者回复: 因为mutex,rwmutex实现locker接口,vet工具能识别出来,waitgroup,cond没有实现locker接口,所以需要嵌入nocopy提示linter工具
2022-09-26归属地:北京 - 授人以🐟,不如授人以渔文章中在描述 Cond 的复杂性时,说明了 3 点,第三点:「条件变量的更改」 是否可需改为:「等待条件的更改」?
作者回复: 对
2021-11-03 - bearlu老师请教一个问题,如果Wait前加锁,然后执行完Wait又Unlock有什么作用,我把Wait后面的Unlock去掉,好似程序也能正常运行。是我漏了什么?
作者回复: 那你locker就永远没释放呗
2021-07-24 - 网管老师,Kubernetes PriorityQueue的那个Pop方法里没有使用p.cond.L.Lock()方法,他们是怎么防止不出现释放未加锁的panic啊。
作者回复: 有锁,看方法第一二行
2020-11-01 - S.S Mr Lin每次调用wait前都要加锁,为啥加锁语句放在了fo的外面?第二次wait是不是就没有加锁了?
作者回复: 放在外面的原因是可以利用锁保护共享数据的读写。wait总是需要锁
2020-10-282
收起评论