这一次,完全搞懂 Go Cond

hi,你们好,我是 haohongfan。web

本篇文章会从源码角度去深刻剖析下 sync.Cond。Go 平常开发中 sync.Cond 多是咱们用的较少的控制并发的手段,由于大部分场景下都被 Channel 代替了。还有就是 sync.Cond 使用确实也蛮复杂的。微信

好比下面这段代码:并发

package main

import (
 "fmt"
 "time"
)

func main() {
 done := make(chan int1)

 go func() {
  time.Sleep(5 * time.Second)
  done <- 1
 }()

 fmt.Println("waiting")
 <-done
 fmt.Println("done")
}

一样可使用 sync.Cond 来实现编辑器

package main

import (
 "fmt"
 "sync"
 "time"
)

func main() {
 cond := sync.NewCond(&sync.Mutex{})
 var flag bool
 go func() {
  time.Sleep(time.Second * 5)
  cond.L.Lock()
  flag = true
  cond.Signal()
  cond.L.Unlock()
 }()

 fmt.Println("waiting")
 cond.L.Lock()
 for !flag {
  cond.Wait()
 }
 cond.L.Unlock()
 fmt.Println("done")
}

大部分场景下使用 channel 是比 sync.Cond方便的。不过咱们要注意到,sync.Cond 提供了 Broadcast 方法,能够通知全部的等待者。想利用 channel 实现这个方法仍是不容易的。我想这应该是 sync.Cond 惟一有用武之地的地方。函数

先列出来一些问题吧,能够带着这些问题来阅读本文:学习

  1. cond.Wait自己就是阻塞状态,为何 cond.Wait 须要在循环内 ?
  2. sync.Cond 如何触发不能复制的 panic ?
  3. 为何 sync.Cond 不能被复制 ?
  4. cond.Signal 是如何通知一个等待的 goroutine ?
  5. cond.Broadcast 是如何通知等待的 goroutine 的?

源码剖析

sync.cond wait

sync.Cond Signal

sync.Cond Broadcast

sync.Cond 排队动图flex

cond.Wait 是阻塞的吗?是如何阻塞的?

是阻塞的。不过不是 sleep 这样阻塞的。spa

调用 goparkunlock 解除当前 goroutine 的 m 的绑定关系,将当前 goroutine 状态机切换为等待状态。等待后续 goready 函数时候可以恢复现场。.net

cond.Signal 是如何通知一个等待的 goroutine ?

  1. 判断是否有没有被唤醒的 goroutine,若是都已经唤醒了,直接就返回了
  2. 将已通知 goroutine 的数量加1
  3. 从等待唤醒的 goroutine 队列中,获取 head 指针指向的 goroutine,将其从新加入调度
  4. 被阻塞的 goroutine 能够继续执行

cond.Broadcast 是如何通知等待的 goroutine 的?

  1. 判断是否有没有被唤醒的 goroutine,若是都已经唤醒了,直接就返回了
  2. 将等待通知的 goroutine 数量和已经通知过的 goroutine 数量设置成相等
  3. 遍历等待唤醒的 goroutine 队列,将全部的等待的 goroutine 都从新加入调度
  4. 全部被阻塞的 goroutine 能够继续执行

cond.Wait自己就是阻塞状态,为何 cond.Wait 须要在循环内 ?

咱们能注意到,调用 cond.Wait 的位置,使用的是 for 的方式来调用 wait 函数,而不是使用 if 语句。指针

这是因为 wait 函数被唤醒时,存在虚假唤醒等状况,致使唤醒后发现,条件依旧不成立。所以须要使用 for 语句来循环地进行等待,直到条件成立为止。

使用中注意点

1. 不能不加锁直接调用 cond.Wait

func (c *Cond) Wait() {
 c.checker.check()
 t := runtime_notifyListAdd(&c.notify)
 c.L.Unlock()
 runtime_notifyListWait(&c.notify, t)
 c.L.Lock()
}

咱们看到 Wait 内部会先调用 c.L.Unlock(),来先释放锁。若是调用方不先加锁的话,会触发“fatal error: sync: unlock of unlocked mutex”。关于 mutex 的使用方法,推荐阅读下《这多是最容易理解的 Go Mutex 源码剖析》

2. 为何不能 sync.Cond 不能复制 ?

sync.Cond 不能被复制的缘由,并非由于 sync.Cond 内部嵌套了 Locker。由于 NewCond 时传入的 Mutex/RWMutex 指针,对于 Mutex 指针复制是没有问题的。

主要缘由是 sync.Cond 内部是维护着一个 notifyList。若是这个队列被复制的话,那么就在并发场景下致使不一样 goroutine 之间操做的 notifyList.wait、notifyList.notify 并非同一个,这会致使出现有些 goroutine 会一直堵塞。

这里留下一个问题,sync.Cond 内部是有一段代码 check sync.Cond 是不能被复制的,下面这段代码能触发这个 panic 吗?

package main

import (
 "fmt"
 "sync"
)

func main() {
 cond1 := sync.NewCond(new(sync.Mutex))
 cond := *cond1
 fmt.Println(cond)
}

有兴趣的能够动手尝试下,以及尝试下如何才能触发这个panic "sync.Cond is copied” 。

sync.Cond 的剖析到这里基本就结束了。有什么想跟我交流的,欢迎评论区留言。


欢迎关注个人公众号,随时关注个人动态

sync.Cond 完整流程图获取连接:连接:https://pan.baidu.com/s/1DjtfCyCua0nGYB6TOSsYWA  密码: wn02。其余模块流程图,请关注公众号回复1获取。

学习资料分享,关注公众号回复指令:

  • 回复 0,获取 《Go 面经》
  • 回复 1,获取 《Go 源码流程图》

本文分享自微信公众号 - HHFCodeRv(hhfcodearts)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索