Go多线程(协程)中互斥锁Mutex的使用
前言
多线程操作切片
在 Go 语言中,可以通过 goroutine 来实现多线程操作同一个切片。
但需要注意的是,在多个 goroutine 中同时操作同一个切片时,可能会出现竞争条件(race condition),因此需要使用互斥锁(Mutex)来保护共享资源,以避免数据竞争问题。
主要使用Mutex实现
1 | var mutex sync.Mutex |
以下是一个示例代码,演示如何在多个 goroutine 中操作同一个切片并使用互斥锁保护:
1 | package main |
在上面的示例中,我们定义了一个切片 slice 和一个互斥锁 mutex,然后启动了 5 个 goroutine 并发地向切片中追加数据。
在每个 goroutine 中对切片的修改都需要先获取互斥锁,然后在操作完成后释放互斥锁。
最后等待所有 goroutine 执行完成后输出最终的切片内容。
sync.Mutex 和 sync.RWMutex 的区别
sync.Mutex(互斥锁)
- 只有一种锁:
Lock()/Unlock()。 - 同一时刻:最多 1 个 goroutine 能进临界区,其它都要等。
- 读和写一视同仁:不管你是只读还是修改,进去都要抢同一把锁。
- 实现简单:不容易在“读/写用错锁类型”上搞混。
sync.RWMutex(读写锁)
- 两种锁:
- 读锁:
RLock()/RUnlock()—— 适合只读共享数据。 - 写锁:
Lock()/Unlock()—— 适合修改共享数据。
- 读锁:
- 同一时刻:
- 可以 多个 goroutine 同时持读锁(并发读)。
- 写锁仍是独占:写的时候不能有别的读锁或写锁。
- 代价:比
Mutex稍重一点(实现更复杂),但在读多写少时能减少阻塞、提高吞吐。
怎么选(实用规则)
- 读远多于写,且读路径里绝不改共享结构 → 倾向
RWMutex(读用RLock,写用Lock)。 - 读写差不多,或临界区很短、逻辑简单 →
Mutex往往够用,也好维护。 - 不能在读锁保护下做写操作;也不要在读锁里再试图“升级”成写锁(容易死锁,要重新设计锁粒度或复制数据再写)。
一句话:Mutex 是“谁进谁独占”;RWMutex 是“多人可以同时读,但写的时候独占”。
RWMutex读写锁
对比
Lock() / Unlock()(写锁)
- 用途:保护“会修改共享数据”的代码段(写 map、改 slice、更新字段等)。
- 特性:同一时刻只允许 1 个 goroutine 持有写锁;并且写锁持有期间,所有读锁/写锁都会被阻塞。
RLock() / RUnlock()(读锁)
- 用途:保护“只读共享数据”的代码段(只遍历、只查找、不改)。
- 特性:同一时刻允许多个 goroutine 同时持有读锁;但如果有人要
Lock()(写锁),新的读锁会被阻塞,写锁会等所有读锁释放后才能拿到。
1 | var stateMu sync.RWMutex |
关键约束(容易踩坑)
拿了
RLock就不能在锁内写共享数据(会造成数据竞争,甚至引发 map 并发崩溃)。不要“读锁升级写锁”:比如先
RLock()再Lock(),容易死锁/卡住。需要写就直接
Lock(),或先RUnlock()再Lock()(中间状态可能变化,要能接受)。必须成对释放:
Lock配Unlock,RLock配RUnlock,不要混用。