共计 2658 个字符,预计需要花费 7 分钟才能阅读完成。
本篇文章会从源码角度去深刻分析下 sync.Cond。Go 日常开发中 sync.Cond 可能是咱们用的较少的管制并发的伎俩,因为大部分场景下都被 Channel 代替了。还有就是 sync.Cond 应用的确也蛮简单的。
比方上面这段代码:
`package main`
`import (`
`"fmt"`
`"time"`
`)`
`func main() {`
`done := make(chan int, 1)`
`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 惟一有用武之地的中央。
先列进去一些问题吧,能够带着这些问题来浏览本文:
- cond.Wait 自身就是阻塞状态,为什么 cond.Wait 须要在循环内?
- sync.Cond 如何触发不能复制的 panic ?
- 为什么 sync.Cond 不能被复制?
- cond.Signal 是如何告诉一个期待的 goroutine ?
- cond.Broadcast 是如何告诉期待的 goroutine 的?
源码分析
sync.cond wait
sync.Cond Signal
sync.Cond Broadcast
sync.Cond 排队动图
cond.Wait 是阻塞的吗?是如何阻塞的?
是阻塞的。不过不是 sleep 这样阻塞的。
调用 goparkunlock
解除以后 goroutine 的 m 的绑定关系,将以后 goroutine 状态机切换为期待状态。期待后续 goready 函数时候可能复原现场。
cond.Signal 是如何告诉一个期待的 goroutine ?
- 判断是否有没有被唤醒的 goroutine,如果都曾经唤醒了,间接就返回了
- 将已告诉 goroutine 的数量加 1
- 从期待唤醒的 goroutine 队列中,获取 head 指针指向的 goroutine,将其重新加入调度
- 被阻塞的 goroutine 能够继续执行
cond.Broadcast 是如何告诉期待的 goroutine 的?
- 判断是否有没有被唤醒的 goroutine,如果都曾经唤醒了,间接就返回了
- 将期待告诉的 goroutine 数量和曾经告诉过的 goroutine 数量设置成相等
- 遍历期待唤醒的 goroutine 队列,将所有的期待的 goroutine 都重新加入调度
- 所有被阻塞的 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 的分析到这里根本就完结了。有什么想跟我交换的,欢送评论区留言。
欢送关注我的公众号,随时关注我的动静
HHFCodeRv
资深 Go 开发, Dubbogo commiter, 分享一些开发日常
9 篇原创内容
公众号
sync.Cond 残缺流程图获取链接:链接:https://pan.baidu.com/s/1Djtf… 明码: wn02。其余模块流程图,请关注公众号回复 1 获取。
学习材料分享,关注公众号回复指令:
- 回复 0,获取《Go 面经》
- 回复 1,获取《Go 源码流程图》