sync.Cond 本身不提供线程安全,必须搭配 sync.Mutex 使用,且所有状态检查必须在锁内完成;其 Wait() 不自动重检条件,需用 for 循环避免虚假唤醒;Signal() 可能丢失,Broadcast() 用于多消费者关闭通知;chan 更简单安全,Cond 仅适用于多条件共享锁的复杂场景。

sync.Cond 本身不提供线程安全,直接用它实现生产者消费者模型容易死锁或丢数据——必须搭配 sync.Mutex 或 sync.RWMutex 使用,且所有共享状态检查必须在锁内完成。
为什么不能只靠 sync.Cond 控制队列进出
sync.Cond 只是条件变量,它不保护任何数据;Cond.Wait() 会自动释放关联的锁,但唤醒后**不会自动重新检查条件是否成立**。常见错误是:生产者发了 Signal(),消费者醒来却发现队列仍为空(虚假唤醒或竞争导致),然后直接读取 panic。
所以必须把「队列是否非空」的判断写在 for 循环里,且整个判断 + 消费逻辑必须包裹在锁中:
mu.Lock()
defer mu.Unlock()
for len(queue) == 0 {
cond.Wait() // 自动解锁,唤醒后自动重锁
}
item := queue[0]
queue = queue[1:]
Cond.Signal() 和 Cond.Broadcast() 怎么选
对单消费者场景,用 Signal() 更轻量;但 Go 的 Signal() 不保证唤醒“正在等待的” goroutine——如果此时没 goroutine 在 Wait,信号就丢失。所以必须确保唤醒前条件已满足,且消费者已进入 Wait 状态(或能重试)。
实际更稳妥的做法:
- 生产者入队后,先改共享状态(如
queue = append(queue, item)),再调cond.Signal() - 消费者始终用
for len(queue) == 0循环等待,不依赖“一次唤醒一定对应一次消费” - 若存在多个消费者且需唤醒全部(比如关闭信号),用
Broadcast();但注意它可能唤醒多余 goroutine,带来无谓竞争
一个最小可运行的带关闭支持的版本
关键点:关闭标志 closed 也是共享状态,必须和队列一起受同一把锁保护;消费者在发现 closed && len(queue) == 0 时退出。
type PCQueue struct {
mu sync.Mutex
cond *sync.Cond
queue []int
closed bool
}
func (q *PCQueue) Produce(item int) {
q.mu.Lock()
defer q.mu.Unlock()
q.queue = append(q.queue, item)
q.cond.Signal()
}
func (q *PCQueue) Consume() (int, bool) {
q.mu.Lock()
defer q.mu.Unlock()
for len(q.queue) == 0 && !q.closed {
q.cond.Wait()
}
if len(q.queue) == 0 && q.closed {
return 0, false
}
item := q.queue[0]
q.queue = q.queue[1:]
return item, true
}
func (q *PCQueue) Close() {
q.mu.Lock()
defer q.mu.Unlock()
q.closed = true
q.cond.Broadcast()
}
注意 Close() 用 Broadcast() 是因为可能有多个消费者在 Wait,都要被通知退出。
比 sync.Cond 更推荐的替代方案
纯通道(chan)在大多数生产者消费者场景下更简单、不易出错:chan 内置同步、支持关闭检测、无虚假唤醒问题。只有当需要「多个不同条件共用一把锁」(比如“队列非空”和“队列未满”共享同一缓冲区锁)时,sync.Cond 才有不可替代性。
如果你只是做标准的生产-消费流水线,优先用 chan;选 sync.Cond 意味着你要主动管理锁粒度、条件重检、关闭传播——这些地方都容易漏掉边界判断。


















