
前一段时间在看某个开源项目的 util 包时,发现一个模块是基于 sync.Cond 开发的
经过包装一下,的的确确能够解决一些业务中的优化场景
doc
基于互斥锁 sync.Mutex 的衍生,核心作用是让多个 goroutine 等待某个 "条件满足",或通知其他等待的 goroutine "条件已满足"
我理解的 Cond 并没有实现新的功能,仅仅是在 Mutex 上的语法糖封装
coding
在一个消息通知系统中,需要发送邮件给一组人,发送完毕后,再执行后面任务,发送邮件是一个很慢的过程
使用 Mutex 可以这么写
func TestSyncMutex(t *testing.T) {
var mu sync.Mutex
go func() { // 邮件发送任务
mu.Lock()
fmt.Println("✅ 邮件发送中")
mu.Unlock()
}()
time.Sleep(1 * time.Second) // 让上面的子协程先拿到锁
mu.Lock()
fmt.Println("✅ 执行邮件发送完毕后的业务")
mu.Unlock()
}
但这里面有个很大隐患,就是在必须要先确保让邮件发送任务拿到锁,通过在主线程睡眠方式太消耗性能
这种方式直接❌
改造成 chan 试一下
func TestSyncChan(t *testing.T) {
state := make(chan struct{})
go func() { // 邮件发送任务
fmt.Println("✅ 邮件发送中")
state <- struct{}{}
}()
<-state
fmt.Println("✅ 执行邮件发送完毕后的业务")
}
这种方式完全没问题✅,解题的思路有多种,再通过 WaitGroup 试一下
func TestSyncWait(t *testing.T) {
wg := sync.WaitGroup{}
wg.Add(1)
go func() { // 邮件发送任务
defer wg.Done()
fmt.Println("✅ 邮件发送中")
}()
wg.Wait()
fmt.Println("✅ 执行邮件发送完毕后的业务")
}
也是没问题的✅
Cond coding
文档中介绍,Cond 是实现条件满足通知其它(>=1)线程
我的理解是,在不使用Cond前,你能想办法在其它线程阻塞等待子线程的信号或者说锁状态,就可以实现业务,例如
- 条件满足,那你自己加自己的业务就是自己的条件满足
- 其它等待的所有线程,你可以把锁或chan放map、list来实现
但既然标注库实现了Cond,那它的语法糖要更加优雅一点
我将它sync.Cond的使用比作是 React 中的 useState 钩子,用于订阅/通知模型,更加容易理解一些
func TestSyncCond(t *testing.T) {
var mu sync.Mutex
cond := sync.NewCond(&mu)
emialDoneState := false
go func() { // 邮件发送任务
fmt.Println("✅ 邮件发送中")
mu.Lock() // 这里的锁纯粹是保障emialDoneState安全的
emialDoneState = true
cond.Broadcast()
mu.Unlock()
}()
mu.Lock() // 这里的锁是配合Cond的
for !emialDoneState {
cond.Wait() // 这里不是for-loop 等待,是系统挂起
}
mu.Unlock()
fmt.Println("✅ 执行邮件发送完毕后的业务")
}
执行逻辑图例如下

核心方法说明
cond.Wait
这是灵魂,他会将Lock解锁,并将程序挂起等待,本质也是一种阻塞等待,替代for循环不断读取emialDoneState变量
cond.Broadcast
告诉所有Wait可以激活了,信号通知
cond.Signal
告诉一个Wait可以激活了,信号通知
当多个任务都在等待某个子任务完成时,Cond的语法就更加优雅一些了
Cond 的几个误区
- 当多个
cond.Wait时,执行广播Signal/Broadcast时,哪个等待者先执行
cond在发送通知后,例如释放1个,标注库注释中没有特别说明,多个等待者谁先执行,没有顺序这一说,如想保证顺序,自己写业务
-
sync.Cond完全替代互斥锁
Cond只是对互斥锁多了几个语法糖,本质还是通过lock来实现,我并不喜欢,正如 sync 包作者的开头说明一样
Other than the Once and WaitGroup types, most are intended for use by low-level library routines.
Higher-level synchronization is better done via channels and communication.