Go 互斥锁mutex
外观
简介[编辑 | 编辑源代码]
互斥锁(Mutex)是Go语言并发编程中用于保护共享资源的核心同步机制,属于sync包的一部分。当多个goroutine需要访问同一块共享数据时,互斥锁确保同一时间只有一个goroutine能进入临界区(Critical Section),从而避免竞态条件(Race Condition)和数据不一致问题。
数学上,互斥锁实现了对共享资源的互斥访问,可用以下公式表示:
基本用法[编辑 | 编辑源代码]
Go的互斥锁通过sync.Mutex
类型实现,提供两个关键方法:
Lock()
:获取锁,若锁已被其他goroutine持有,则当前goroutine阻塞Unlock()
:释放锁,允许其他goroutine获取锁
示例代码[编辑 | 编辑源代码]
package main
import (
"fmt"
"sync"
)
var counter int
var mu sync.Mutex
func increment() {
mu.Lock() // 加锁
counter++ // 临界区
mu.Unlock() // 解锁
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
increment()
}()
}
wg.Wait()
fmt.Println("Final counter:", counter) // 正确输出: 1000
}
输出:
Final counter: 1000
关键点:
- 不加锁时,多个goroutine同时修改
counter
会导致结果不确定 - 使用
defer mu.Unlock()
是更安全的做法,可避免忘记释放锁
底层原理[编辑 | 编辑源代码]
Go的互斥锁通过组合以下机制实现:
- 自旋锁:短期等待时循环检查锁状态
- 等待队列:长期等待的goroutine会被放入队列
- 原子操作:底层依赖
atomic
包的CAS(Compare-And-Swap)操作
高级用法[编辑 | 编辑源代码]
读写锁(RWMutex)[编辑 | 编辑源代码]
当读操作远多于写操作时,sync.RWMutex
效率更高:
- 允许多个读锁同时存在
- 写锁独占,与任何其他锁互斥
var rwMu sync.RWMutex
func readData() {
rwMu.RLock() // 读锁
defer rwMu.RUnlock()
// 读取共享数据
}
func writeData() {
rwMu.Lock() // 写锁
defer rwMu.Unlock()
// 修改共享数据
}
实际案例[编辑 | 编辑源代码]
银行账户转账[编辑 | 编辑源代码]
type Account struct {
balance int
mu sync.Mutex
}
func (a *Account) Transfer(to *Account, amount int) {
a.mu.Lock()
defer a.mu.Unlock()
to.mu.Lock()
defer to.mu.Unlock()
a.balance -= amount
to.balance += amount
}
死锁预防技巧:
1. 固定获取锁的顺序(如按账户ID排序)
2. 使用sync.Map
替代手动锁管理
3. 设置锁超时(通过select
+time.After
)
性能考量[编辑 | 编辑源代码]
- 锁粒度:细粒度锁(保护最小必要数据)优于粗粒度锁
- 锁持续时间:临界区应尽可能短
- 测量工具:使用
go test -bench
和pprof
分析锁竞争
场景 | Mutex | RWMutex(读) | atomic |
---|---|---|---|
无竞争 | 15-20 | 25-30 | 5-10 |
高竞争 | 200+ | 50(读) / 200+(写) | N/A |
常见错误[编辑 | 编辑源代码]
- 忘记解锁(导致死锁)
- 重复解锁(引发panic)
- 锁拷贝(值传递会使锁失效)
- 嵌套锁(容易导致死锁)
最佳实践[编辑 | 编辑源代码]
1. 使用defer
确保解锁
2. 为包含锁的结构体定义String()
方法时避免触发锁
3. 考虑使用sync/atomic
替代简单计数器
4. 在高并发场景测试锁竞争
页面模块:Message box/ambox.css没有内容。
不要将Mutex作为结构体的嵌入字段,这会意外暴露Lock/Unlock方法 |