在 Go 项目中安全使用全局变量配合 mutex 锁,核心是将全局变量与 sync.Mutex 绑定,并在所有读写该变量的 goroutine 中通过 Lock() 和 Unlock() 包裹临界区代码。此方案适用于多协程并发读写共享数据的场景,风险边界在于忘记解锁或锁粒度控制不当可能导致死锁或性能下降。
先说结论:全局变量必须配合互斥锁保护临界区,否则并发读写会导致数据竞争。
- 先判断:确认变量是否真的需要共享,能否改为局部变量或 channel 传递
- 优先做:使用 defer 解锁,确保异常发生时也能释放锁
- 再验证:配合 go run -race 检测竞态条件,确保无警告
快速处理思路
定义全局锁变量,在访问全局数据前加锁,操作完成后解锁。
var (
count int
mu sync.Mutex
)
func safeIncrement() {
mu.Lock()
defer mu.Unlock()
count++
}为什么会这样
直接读写全局变量在 goroutine 中会引发数据竞争,因为自增操作不是原子性的。
Go 的 goroutine 轻量,但共享变量没加锁时,多个 goroutine 同时读写同一块内存,会导致数据竞争(race condition)。
count++ 不是原子操作,它包含读、修改、写回三个步骤,多个 goroutine 会互相覆盖。
分步处理
1. 定义锁变量:将 sync.Mutex 定义为全局变量或结构体字段,不要定义为局部变量。
2. 包裹临界区:在所有读写该全局变量的地方,使用 mu.Lock() 和 mu.Unlock() 包裹。
3. 防止漏解锁:推荐使用 defer mu.Unlock() 确保即使发生 panic 也能释放锁。
4. 避免耗时操作:不要在锁内执行 HTTP 请求或文件读写,防止阻塞其他 goroutine。
怎么验证是否生效
使用 Go 自带的竞态检测器运行代码,观察是否有 DATA RACE 警告。
go run -race main.go如果输出中包含 WARNING: DATA RACE,说明加锁未生效或覆盖不全;若无警告且结果符合预期,说明并发安全。
常见坑
1. 复制含 Mutex 的结构体:Mutex 不能被复制,复制结构体会导致锁失效。
2. 读写锁混用:使用 sync.RWMutex 时,写操作必须用 Lock(),读操作必须用 RLock(),不可混用。
3. 重复加锁:同一 goroutine 内对同一 Mutex 重复加锁会导致死锁。
常见问题
什么时候用 atomic 代替 mutex?
简单变量计数场景可用 atomic,性能更高;复杂结构体或需保护多变量一致性时用 mutex。
sync.RWMutex 适合什么场景?
适合读多写少的场景,允许多个 goroutine 并发读,但写操作独占。
全局变量一定要加锁吗?
只有被多个 goroutine 并发读写时才需要加锁,只读或单协程访问不需要。
参考来源
- Go:多个协程访问同一个全局变量 (利用 mutex 锁)
- 【加锁】通过 go 的 mutex 和 atomic 完成全局变量加锁
- Golang 怎么用 Mutex 保护共享变量_Golang 如何加锁防止多协程同时修改数据【实战】
- Golang 如何使用 mutex 保证数据安全_Golang mutex 互斥锁实践
- Go 语言中的 Mutex:并发安全的守护者
- 20 - Go 互斥锁:Mutex 与并发安全
- Go 中的 Mutex 与 RWMutex:你真的用对了吗?