
sync.cond 是 go 中用于多 goroutine 协作等待共享条件变化的同步原语,但其正确用法要求严格遵循“加锁→检查条件→等待→解锁”循环模式,否则极易引发死锁或竞态;本文详解其核心契约、典型范式及常见陷阱。
sync.cond 是 go 中用于多 goroutine 协作等待共享条件变化的同步原语,但其正确用法要求严格遵循“加锁→检查条件→等待→解锁”循环模式,否则极易引发死锁或竞态;本文详解其核心契约、典型范式及常见陷阱。
sync.Cond 并非独立的锁,而是一个条件变量协作者——它必须与一个 sync.Locker(通常是 *sync.Mutex 或 *sync.RWMutex)配合使用,且所有对共享状态的读写、以及对 Wait() 的调用,都必须在该 Locker 的保护下进行。其设计哲学是:Wait 操作会自动释放锁,并在被唤醒后重新获取锁,因此调用 c.Wait() 前必须已持有 c.L 所指向的锁,否则将 panic;而唤醒后,goroutine 会重新持锁并继续执行,此时必须再次检查条件是否真正满足(即“虚假唤醒”防护),因此标准用法必为 for !condition { c.Wait() } 循环。
以下是一个符合规范的典型示例,模拟多个 goroutine 等待 HTTP 响应头就绪的场景:
package main
import (
"fmt"
"sync"
"time"
)
var (
headers map[string]string // 共享资源:HTTP 头信息
mu sync.Mutex
cond = sync.NewCond(&mu)
)
func waitForHeaders() {
mu.Lock()
defer mu.Unlock()
// 关键:必须用 for 循环检查条件,防范虚假唤醒
for headers == nil {
cond.Wait() // Wait 会自动释放 mu,唤醒后重新获取 mu
}
fmt.Printf("Received headers: %+v\n", headers)
}
func setHeaders() {
time.Sleep(2 * time.Second) // 模拟下载延迟
mu.Lock()
defer mu.Unlock()
headers = map[string]string{
"Content-Type": "application/octet-stream",
"Content-Length": "10485760",
"Last-Modified": "Wed, 01 Jan 2025 00:00:00 GMT",
}
cond.Broadcast() // 通知所有等待者条件可能已满足
}
func main() {
var wg sync.WaitGroup
// 启动多个等待者
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
fmt.Printf("Goroutine %d waiting for headers...\n", id)
waitForHeaders()
}(i)
}
// 启动生产者(设置 headers)
go setHeaders()
wg.Wait()
}✅ 关键要点总结:
- 锁必须由 Cond 持有:sync.NewCond(&mu) 中的 mu 必须是后续所有 Wait/Signal/Broadcast 操作所依赖的同一把锁;
- Wait 前必须已加锁:c.Wait() 调用前,当前 goroutine 必须已通过 c.L.Lock() 获取锁,否则 panic;
- 永远使用 for 循环检查条件:for !condition { c.Wait() } 是强制约定,不可简化为 if !condition { c.Wait() };
- 唤醒不保证条件成立:Signal/Broadcast 仅表示“条件可能已满足”,唤醒后的 goroutine 必须重新持锁并验证;
- Broadcast vs Signal:Broadcast 唤醒所有等待者(适合“状态就绪”类场景),Signal 唤醒一个(适合“任务队列取任务”类场景);
- 避免在 Wait 后直接操作共享数据而不加锁:Wait 返回时已重新持锁,所有共享数据访问仍需在锁保护下完成。
⚠️ 注意事项:
虽然 sync.Cond 在多读者单写者等待场景中有效,但若业务逻辑允许,优先考虑 channel + once.Do 或 channel + atomic.Value 等更简洁、更不易出错的方案。例如,对“等待 headers 就绪”这一需求,可封装为:
var (
headerCh = make(chan map[string]string, 1)
once sync.Once
)
func GetHeaders() map[string]string {
once.Do(func() {
go func() {
// 模拟下载逻辑
time.Sleep(2 * time.Second)
headerCh <- map[string]string{ /* ... */ }
}()
})
return <-headerCh // 阻塞直到 headers 可用
}这种基于 channel 的方式天然支持多消费者、无锁、语义清晰,是 Go 生态中更推荐的协作模式。sync.Cond 应作为 channel 不适用时(如需复用同一条件多次通知、或与复杂状态机深度耦合)的补充工具,而非首选。

















