Golang中sync包有哪些常用组件_Golang并发同步工具详解

1次阅读

go sync包需按场景精准选用原语:mutex比rwmutex更轻量安全(读字段时);rwmutex不支持递归读锁且易致写饥饿;once.do不保证执行成功;waitgroup.add须在goroutine启动前调用;pool仅适用于无状态临时对象

Golang中sync包有哪些常用组件_Golang并发同步工具详解

Go 的 sync 包不是“拿来就用”的工具箱,而是需要按场景精准选用的底层同步原语集合;误配或滥用会导致死锁、竞态、性能陡降甚至逻辑错误。

什么时候该用 sync.Mutex 而不是 sync.RWMutex

别一看到“读多写少”就默认上 RWMutex——它只在读操作明显耗时(比如深拷贝结构体json 序列化)且并发goroutine 数量高时才有收益。

  • 如果只是读一个 intbool 字段,sync.Mutex 更轻量、更安全;RWMutex 的额外状态管理反而增加调度开销
  • RWMutex 不支持递归读锁:同一个 goroutine 多次调用 RLock() 后只调一次 RUnlock() 会 panic
  • 持续有新读请求进来时,写操作可能被无限期饿死(read starvation),尤其在高吞吐配置服务中要警惕
  • 不能在持有 RLock() 时尝试升级为写锁(即再调 Lock()),必然死锁

sync.Once.Do 的真实行为和常见误用

sync.Once.Do 保证函数「最多执行一次」,但不保证「执行成功」——这是最常被忽略的语义细节。

  • 如果传入的函数 panic,Once 会标记为“已执行”,后续调用直接返回,不会重试
  • 不要把含 fallback、重试或 Error 返回处理的逻辑直接塞进 Do;应在外层封装,配合显式状态标志位
  • 正确姿势是:用 initErrinited 双变量暴露初始化结果,而不是依赖 Once 自身状态

sync.WaitGroup 的 Add 为什么必须在 goroutine 启动前调用

这不是风格问题,而是内存可见性问题:WaitGroup.AddDone 必须处于同一个 happens-before 链路中,否则 Wait() 永远阻塞。

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

  • wg.Add(1) 放在 go func() 内部 → Done 执行了,但主 goroutine 根本看不到计数器变化 → Wait() 卡死
  • 动态启动 goroutine 时,必须在循环外预设总数,或用闭包捕获当前值,例如:go func(val int) { defer wg.Done(); process(val) }(item)
  • WaitGroup 不能被复制:结构体中嵌了 sync.WaitGroup,传参必须用指针,否则副本间计数器完全隔离

sync.Pool 不是缓存,而是一把双刃剑

sync.Pool 的对象生命周期由 GC 控制,它不保证复用,也不承诺不清理——只适合「高频创建销毁 + 对象无强状态 + 可安全清空」的临时资源。

  • 不要往里放含 channel、未关闭文件句柄、或带闭包引用的对象
  • 对象放回池前必须手动清空敏感字段(如 userTokenpassword),否则可能泄露给下一个使用者
  • New 函数仅在池为空时触发,不保证每次 Get 都调用;测试时可用 GODEBUG=gctrace=1 观察实际复用率

真正难的从来不是记住 API,而是判断哪个原语能建立正确的 happens-before 关系、哪个锁粒度既安全又不过度阻塞、以及哪次 panic 其实暴露了初始化逻辑本身的设计缺陷。

text=ZqhQzanResources