Go: 标准库 sync
Outlinks (1)
Backlinks (1)
Backlinks (1)
1 · 标准库 sync#
提供同步原语:锁、等待组、单例、对象池、条件变量、支持并发的map
同步原语就是多个执行单元(goroutine)之间协调行为的基本工具
go doc sync
| 协调方式 | 类型 | 用途 | 描述 |
|---|---|---|---|
| 互斥 | Mutex / RWMutex | 互斥锁 / 读写锁 | 同一时刻只有一个能访问;RW 优化读多写少,读共享写互斥 |
| 等全部完成 | WaitGroup | 等待一组 goroutine 完成 | Add 设总数,Done 减一,Wait 阻塞直到归零 |
| 只执行一次 | Once / OnceFunc / OnceValue | 确保函数只执行一次 | 多个 goroutine 同时调用,函数体只执行一次(懒初始化) |
| 信号通知 | Cond | 条件变量,用于 goroutine 间信号通知 | #Cond |
| 并发安全访问 | Map | 并发安全的 map(适合读多写少或 key 不相交场景) | 内置并发安全,无需额外加锁 |
| 复用对象 | Pool | 临时对象池,减少 GC 压力 | 多 goroutine 共享对象池,Get/Put 并发安全,非传统同步原语 |
1.1 · Cond#
围绕共享状态做等待/通知协调的机制
- Cond 本身并不保存这个条件,需要自定义条件:通常是
通知方修改条件,等待方检查条件; - Cond 必须配合一把锁一起用,通常是 sync.Mutex
- 等待方调用
Wait()时必须已经持有锁 Wait()会”原子地”先解锁再睡眠(阻塞),被唤醒后会重新加锁(需要竞争锁)再返回- 一定要写成
for !condition { cond.Wait() },不要写成 if:被唤醒时,条件不一定还成立
一般流程是:
- 等待方先加锁,检查条件
- 条件不满足,Wait() 挂起
- 通知方加锁,修改条件
- 通知方调用 Signal/Broadcast
- 等待方被唤醒,重新竞争拿锁,再次检查条件
重要点:
- 条件需要自定义:通常是
通知方修改条件,但是等待方也可是改(表示消费掉了这个条件) - 等待方被唤醒后,还是需要竞争锁:也就是说
Broadcast唤醒全部后,也是排队竞争锁
NOTE: Cond 将数据、通知拆分了,不是一个原子整体,增加心智负担;chan的数据、绑定一起了。
2 · sync vs. chan#
chan 也是用于协调goroutine的工具,但是与sync有点不同。chan 以通信的方式,而sync使用共享内容的方式。
| ksync 原语 | chan | |
|---|---|---|
| 思路 | 通过共享内存通信 | 通过通信共享内存 |
| 关注点 | 保护数据,控制访问权 | 传递数据,协调流程 |
| 典型场景 | 计数器、缓存、对象池 | 流水线、扇入扇出、信号通知 |
| 粒度 | 更底层,性能更好 | 更高层,表达力更强 |
x/sync#
| 协调方式 | 类型 | 用途 |
|---|---|---|
| 等全部完成(带错误) | errgroup | #errgroup |
| 合并重复 | singleflight | #singleflight |
| 限制并发数 | semaphore | #semaphore |
3.1 · errgroup#
带错误处理和取消能力的 WaitGroup,任一出错可取消全部
3.2 · singleflight#
合并重复调用,同 key 只执行一次
相同key,只调用最开始的,后续的都不执行,只等待
3.3 · semaphore#
同时最多允许多少个任务占用资源
权重:每个任务占用的资源权重可以不一样