sync.Mutex 用于保护临界区确保互斥访问,sync.Cond 需配合 Mutex 实现条件等待与通知;二者协同解决“谁可以进”和“等什么再进”的问题,使用时须遵循锁→检查→等待/修改→通知→解锁流程。

在 Go 中,sync 包提供了基础的并发原语,其中 互斥锁(sync.Mutex) 和 条件变量(sync.Cond) 是控制共享资源访问、协调 goroutine 执行的关键工具。它们不单独使用,而是配合协作:Mutex 保证临界区安全,Cond 在满足特定条件时唤醒等待的 goroutine。
用 sync.Mutex 保护共享数据
Mutex 是最常用的同步机制,用于确保同一时刻只有一个 goroutine 能进入临界区。它不关心“为什么等”,只负责“谁可以进”。
- 定义一个
sync.Mutex字段(通常作为结构体成员),避免复制;若必须传递,传指针 - 用
mu.Lock()进入临界区,mu.Unlock()离开 —— 推荐用defer mu.Unlock()防止遗漏 - 不要在持有锁时做耗时操作(如网络请求、大量计算),否则会阻塞其他 goroutine
示例:安全地累加计数器
type Counter struct {
mu sync.Mutex
value int
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.value++
}
func (c *Counter) Load() int {
c.mu.Lock()
defer c.mu.Unlock()
return c.value
}
用 sync.Cond 实现条件等待与通知
sync.Cond 本身不提供互斥能力,必须和一个已有的 sync.Locker(通常是 *sync.Mutex)绑定。它解决的是“等某个条件成立再继续”的问题,比如“队列非空才取元素”或“缓冲区有空位才写入”。
立即学习“go语言免费学习笔记(深入)”;
- 创建 Cond:用
sync.NewCond(&mu),其中&mu是指向 Mutex 的指针 - 等待条件:先加锁,检查条件是否满足;不满足则调用
cond.Wait()—— 它会自动释放锁并挂起 goroutine;被唤醒后自动重新加锁,所以需再次检查条件(防止虚假唤醒) - 通知等待者:用
cond.Signal()唤醒一个,或cond.Broadcast()唤醒全部;通知前通常要修改共享状态,并确保在锁保护下完成
示例:实现一个线程安全的阻塞队列
type BlockingQueue struct {
mu sync.Mutex
cond *sync.Cond
items []int
capacity int
}
func NewBlockingQueue(cap int) *BlockingQueue {
q := &BlockingQueue{
capacity: cap,
items: make([]int, 0),
}
q.cond = sync.NewCond(&q.mu)
return q
}
func (q *BlockingQueue) Push(x int) {
q.mu.Lock()
defer q.mu.Unlock()
// 等待有空位
for len(q.items) >= q.capacity {
q.cond.Wait()
}
q.items = append(q.items, x)
q.cond.Broadcast() // 通知可能等待读取的 goroutine
}
func (q *BlockingQueue) Pop() int {
q.mu.Lock()
defer q.mu.Unlock()
// 等待非空
for len(q.items) == 0 {
q.cond.Wait()
}
x := q.items[0]
q.items = q.items[1:]
q.cond.Broadcast() // 通知可能等待写入的 goroutine
return x
}
常见误区与注意事项
使用 Cond 时容易出错,关键点在于逻辑顺序和状态一致性:
- Wait 必须在循环中调用:因为可能存在虚假唤醒(spurious wakeup),不能只靠一次判断就进入等待
- 所有对共享状态的读写都必须在锁内完成:包括通知前的状态更新和 Wait 前的条件检查
- Cond 不是替代 channel 的通用方案:对于简单的生产者-消费者模型,channel 更简洁、更符合 Go 的惯用法;Cond 更适合需要精细控制唤醒策略或复用锁的复杂同步场景
- 不要复制 Cond 或 Mutex:它们包含运行时状态,复制会导致未定义行为;始终传递指针
对比 channel:何时选 Cond?
Go 推崇 “通过通信共享内存”,所以大多数场景优先用 channel。但 Cond 仍有其适用位置:
- 多个 goroutine 等待**同一个条件**,且该条件由多个不同事件共同影响(例如:任务完成 + 资源就绪 + 权限验证通过)
- 需要在已有锁结构上扩展等待逻辑(如封装在复杂对象内部,锁已存在)
- 性能敏感场景下,避免 channel 的额外内存分配和调度开销(极少数情况)
简单说:能用 channel 就别硬上 Cond;需要用 Cond 时,一定配好 Mutex,且严格遵循“锁 → 检查 → 等待 / 修改 → 通知 → 解锁”流程。










