并发同步
Channel 管通信,同步原语管共享状态。面试主线:五个原语各管什么、Context 怎么传播取消。
同步原语
| 原语 | 用途 | 注意 |
|---|---|---|
| Mutex | 互斥锁:临界区 | 别在锁内做 IO(长时间持锁) |
| RWMutex | 读写锁:读共享写互斥 | 读多写少;写者可能饿死(可配写优先) |
| WaitGroup | 等一组 goroutine 完成 | Add 要在启动前;Done 用 defer |
| Once | 只执行一次(单例初始化) | sync.Once 内部有锁,线程安全 |
| atomic | 原子操作:计数器、标志 | 比锁轻量,只适用简单类型 |
var wg sync.WaitGroup
for _, task := range tasks {
wg.Add(1) // 启动前 Add
go func(t Task) {
defer wg.Done() // defer 保证计数
process(t)
}(task) // 注意: 传参, 别捕获循环变量
}
wg.Wait() // 等全部完成Mutex 考点:
- 不可复制(拷贝后锁失效)、不可重入(同 goroutine 再 Lock 死锁)
sync.Mutexvssync.RWMutex:写多用 Mutex,读多写少用 RWMutex- 锁的粒度:锁内只放临界操作(共享数据访问),别包业务逻辑
atomic:atomic.AddInt64、atomic.Load/Store、atomic.CompareAndSwap。计数器场景比 Mutex 快一个量级;CAS 是自旋锁和乐观锁的基础(见 Redis 分布式锁的 Lua 对比)。
Context:取消传播 ★
ctx, cancel := context.WithCancel(parentCtx)
ctx, cancel := context.WithTimeout(parentCtx, 3*time.Second)
defer cancel()
go worker(ctx) // 把 ctx 传给 goroutine
// worker 内部:
select {
case <-ctx.Done(): // 收到取消信号
return
case <-dataCh:
// 正常处理
}Context 树: 取消沿传递链级联
- Context 沿调用链传递,取消从根向叶子级联
- 作用:超时控制(WithTimeout)、取消传播(cancel)、携带值(WithValue,慎用)
- 不传 Context 的后果:goroutine 无法感知取消 → 泄漏(见 goroutine 泄漏篇)
- 规范:函数第一个参数是 ctx(
func f(ctx context.Context, ...)),内部监听 ctx.Done()
面试追问
- 五个同步原语? Mutex 互斥、RWMutex 读写、WaitGroup 等待、Once 单次、atomic 原子。各管一摊
- WaitGroup 的注意点? Add 在 goroutine 启动前,Done 用 defer。循环变量要传参
- Mutex 不能做什么? 不可重入(同 goroutine 再锁死锁)、不可复制。锁内别做 IO
- Context 怎么取消? WithCancel/WithTimeout 生成,cancel 沿传递链级联。goroutine 监听 ctx.Done()
- atomic 和 Mutex? atomic 轻量(无锁),适用计数/标志;Mutex 适用复杂临界区。CAS 是乐观锁基础