共计 2229 个字符,预计需要花费 6 分钟才能阅读完成。
hi,大家好,我是 haohongfan。
本篇文章会从源码角度去深刻分析下 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 的?
源码分析
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”。