Go 并发进阶:sync.Cond 条件变量与互斥锁的协作精髓
在 Go 语言并发编程中,互斥锁(sync.Mutex/sync.RWMutex)是保护共享资源的基础,但仅靠互斥锁往往无法高效解决'等待共享资源状态变更'的问题——如果线程/协程需要循环检查资源状态,会造成不必要的 CPU 消耗,甚至降低程序的并发性能。而条件变量(sync.Cond) 正是为解决这一痛点而生,它基于互斥锁实现,能在共享资源状态变化时主动通知等待的协程,让并发协作更高效、更优雅。
本文将从'原理 + 实战'双维度,拆解 sync.Cond 与互斥锁的配合逻辑,带你彻底搞懂条件变量的使用姿势。
一、先搞懂:条件变量不是'锁',是'通知器'
很多初学者会把条件变量和互斥锁混为一谈,但二者的核心定位完全不同:
- 互斥锁:保护临界区和共享资源,保证同一时刻只有一个协程操作资源;
- 条件变量:不保护资源,而是协调访问资源的协程——当共享资源状态不满足操作条件时,让协程'等待通知';当状态满足时,主动'通知'等待的协程。
用一个生动的场景类比: 假设你和我执行秘密任务,共享一个'信箱'(共享资源):我负责放情报,你负责取情报,信箱同一时刻只能被一个人打开(互斥锁的作用)。如果我去放情报时发现信箱非空,没必要反复去检查(轮询),只需等你取走后收到'蓝帽子小孩'的通知;如果你去取情报时发现信箱为空,也只需等我放入后收到'红帽子小孩'的通知。这里的'红蓝帽子小孩',就是条件变量的核心——状态变更的通知器。
二、sync.Cond 的核心:与互斥锁的绑定规则
sync.Cond 无法单独使用,必须与互斥锁(sync.Mutex/sync.RWMutex)绑定,其核心方法(Wait/Signal/Broadcast)的使用也完全依赖互斥锁的状态:
1. sync.Cond 的初始化:必须绑定互斥锁
sync.Cond 没有'开箱即用'的零值,必须通过 sync.NewCond() 函数初始化,该函数要求传入一个 sync.Locker 接口实现(互斥锁的指针):
import (
"log"
"sync"
"time"
)
// 示例 1:基于普通互斥锁初始化
var mu sync.Mutex
cond := sync.NewCond(&mu)
// 示例 2:基于读写锁的读锁/写锁初始化
var rwMu sync.RWMutex
// 绑定写锁(Lock/Unlock)
sendCond := sync.NewCond(&rwMu)
// 绑定读锁(RLock/RUnlock):通过 RLocker() 做方法代理
recvCond := sync.NewCond(rwMu.RLocker())
sync.RWMutex 的 RLocker() 方法会返回一个代理对象,其 Lock()/Unlock() 方法内部会调用读锁的 RLock()/RUnlock(),以此适配 sync.Locker 接口。
2. 核心方法的使用规则(重中之重)
sync.Cond 提供三个核心方法,其使用必须遵循与互斥锁的配合规则:
| 方法 | 作用 | 互斥锁状态要求 |
|---|---|---|
Wait() | 等待通知 | 调用前必须持有绑定的互斥锁(已 Lock/RLock) |


