sync.Map 并非无锁,仅读路径避免全局锁,写操作仍需加锁,适用于读多写少场景如配置缓存;channel 是运行时实现的无锁队列;sync.Cond 必须配合互斥锁使用;原子操作+关闭 channel 可实现零锁信号同步。

sync.Map 适合读多写少,但不是“无锁”
很多人误以为 sync.Map 是完全无锁的,其实它只是在**读路径上尽量避免全局锁**:读操作会先查只读 map(readOnly),命中就直接返回;未命中才加锁去查 dirty map。写操作仍需锁住整个结构体的 mu 字段。
所以它不适用于高频写场景——一旦触发 dirty 到 readOnly 的提升,或频繁 miss 导致反复加锁,性能反而比加锁的 map + sync.RWMutex 更差。
- ✅ 适用:配置缓存、白名单、统计计数器等一天写几次、每秒读几千次的场景
- ❌ 不适用:实时更新的用户 session 状态、高频指标聚合(如每毫秒更新一次的吞吐量)
- ⚠️ 注意:
sync.Map不支持遍历安全,Range是快照语义,期间新增/删除不会被看到
channel 是 Go 并发协调的默认无锁方案
Go 的设计哲学是“通过通信共享内存”,chan 本身就是运行时实现的无锁队列(底层用环形缓冲区 + 原子状态机)。多个 goroutine 从同一 channel 接收数据时,调度器保证:
- 每次发送,至多一个接收者被唤醒(无重复消费)
- 没有轮询顺序,但唤醒高度公平(不是随机,也不是 FIFO,而是伪随机+饥饿防护)
- 接收端阻塞时,无需任何锁参与,仅靠 goroutine 状态切换和队列指针原子操作完成同步
比如启动 10 个 worker 处理任务:jobs := make(chan int, 100),所有 for j := range jobs 都天然并发安全,无需额外同步原语。
立即学习“go语言免费学习笔记(深入)”;
sync.Cond 必须配锁,不能单独“无锁”使用
sync.Cond 本身不提供互斥保护,它必须绑定一个 sync.Mutex 或 sync.RWMutex 实例(通过 NewCond(l Locker))。调用 Wait 前必须已持有该锁,且 Wait 会自动释放锁并挂起 goroutine;被唤醒后又自动重新获取锁。
- 常见错误:在未加锁时调用
c.Wait()→ 直接 panic: “sync: Cond.Wait called without holding mutex” - 广播时机关键:应在修改完共享条件后、且仍持锁时调用
c.Broadcast()或c.Signal(),否则可能漏通知 - 典型陷阱:用
RWMutex作锁时,若在读锁下调用Wait,会因无法降级为写锁而卡死——Wait要求锁可重入且能完整释放,RWMutex的读锁不满足
真正无锁协调:原子操作 + 信号 channel 组合
对极简状态协同(比如“所有 goroutine 启动完成”或“某个开关翻转”),可以用 sync/atomic 搭配一个关闭 channel 来实现零锁协作:
var started int32
ready := make(chan struct{})
go func() {
atomic.StoreInt32(&started, 1)
close(ready) // 关闭即广播
}()
// 其他 goroutine:
<-ready // 等待信号
if atomic.LoadInt32(&started) == 1 {
// 安全读取启动状态
}
这种模式避开了锁竞争和调度器唤醒延迟,适合初始化同步、信号量翻转等低频控制点。但注意:它不适用于需要维护复杂共享状态(如计数器增减、map 更新)的场景——那必须回归 sync.Mutex 或 sync.Map。


















