Golang如何使用sync包进行同步操作_Golang sync包并发控制方法

14次阅读

必须用 sync.Mutex 时:多 goroutine 同时读写共享变量且无其他同步机制;漏锁、锁内耗时、错误初始化、未 defer 解锁均会导致 data race 或死锁。

Golang如何使用sync包进行同步操作_Golang sync包并发控制方法

什么时候必须用 sync.Mutex

当多个 goroutine 同时读写同一个变量(比如计数器、状态标志、缓存 map)且没有其他同步机制(如 channel)兜底时,sync.Mutex 是最直接、最可控的选择。它不解决“谁先来”,只确保“一次只让一个进来”。

  • 漏锁是最高频错误:比如只对写加锁,但读操作没锁 → go run -race 会立刻报 data race
  • 别在锁里做耗时事:http 调用、大循环、阻塞 IO 都会让其他 goroutine 卡住,哪怕只是 10ms,高并发下也会雪球式拖慢整体吞吐
  • 结构体字段声明 mu sync.Mutex 就行,千万别 new(sync.Mutex) 或赋值给另一个变量 —— Go 1.19+ 会 panic
  • defer mu.Unlock() 是底线,但注意:如果函数中间有 return 且上面没 defer,锁就永远不释放 → 死锁

sync.RWMutex 真的比 Mutex 快吗?

快,但只在「读远多于写」的场景下成立。它的本质是把读和写解耦:多个 RLock() 可以并行,但只要有一个 Lock() 在等,新来的 RLock() 就得排队。

  • 写饥饿风险真实存在:如果写操作频繁(比如每秒几十次),读请求可能长期等不到机会,尤其在高负载时
  • 不能“读锁升级为写锁”:持有 RLock() 时调 Lock() 会死锁,必须先 RUnlock()Lock()
  • 配置缓存、状态映射表这类读取密集型结构,用 RWMutex 能提升 2–5 倍吞吐;但如果是写多读少(比如日志聚合器),反而更慢

为什么 sync.WaitGroup 总是 Wait 不住?

根本原因只有一个:Add() 没在 goroutine 启动前调用。WaitGroup 的计数器不是原子“监听”,而是靠你手动配平。

  • wg.Add(1) 必须出现在 go func() { ... }() 之前,否则线程可能已经执行到 wg.Wait(),而计数还是 0 → 立刻返回
  • 别在 goroutine 里调 wg.Add():它和 Wait() 之间没有 happens-before 关系,行为未定义
  • 循环变量陷阱:写 for i := 0; i 会全打印 3 —— 改成 go func(v int) { fmt.Println(v) }(i)

sync.Onceatomic.CompareAndSwap 该选哪个?

sync.Once 是开箱即用的安全初始化方案;atomic.CompareAndSwap 是更底层、更轻量的控制权,但要自己兜底全部逻辑。

立即学习go语言免费学习笔记(深入)”;

  • 加载配置、初始化 DB 连接池、启动后台协程 —— 用 Once.Do(),简洁无错
  • 需要细粒度控制状态流转(比如 “初始化中 / 初始化失败 / 已就绪” 三态),或性能极端敏感(微秒级延迟要求),才考虑 atomic 手写
  • Once 内部 panic 不会传播,但如果你的初始化函数 panic,后续所有 Do() 调用都会 panic —— 所以务必在 Dorecover

真正难的从来不是记住 API,而是判断哪条路径没加锁、哪个 WaitGroup 计数没对上、哪种场景下 RWMutex 其实拖了后腿。这些地方没有银弹,只能靠 -race 跑、用压测看、在日志里埋点验证。

text=ZqhQzanResources