Go并发同步原语详解
sync包提供底层同步原语,用于保护共享资源和协调goroutine。
Mutex互斥锁
基本用法
Go
var mu sync.Mutex
var counter int
func increment() {
mu.Lock()
counter++
mu.Unlock()
}
防止死锁
Go
// 错误:忘记Unlock
func bad() {
mu.Lock()
if condition {
return // 死锁!
}
mu.Unlock()
}
// 正确:使用defer
func good() {
mu.Lock()
defer mu.Unlock() // 确保释放
if condition {
return // 安全
}
}
永远用defer释放锁,防止死锁。
RWMutex读写锁
读多写少场景
Go
var rwmu sync.RWMutex
var data map[string]string
func read(key string) string {
rwmu.RLock() // 读锁,可并行
v := data[key]
rwmu.RUnlock()
return v
}
func write(key, val string) {
rwmu.Lock() // 写锁,互斥
data[key] = val
rwmu.Unlock()
}
| 锁类型 | 读操作 | 写操作 | 适用场景 |
|---|---|---|---|
| Mutex | 互斥 | 互斥 | 读写均衡 |
| RWMutex | 并行 | 互斥 | 读多写少 |
WaitGroup等待组
等待多个goroutine完成
Go
var wg sync.WaitGroup
func main() {
for i := 0; i < 5; i++ {
wg.Add(1) // 增加计数
go func(n int) {
defer wg.Done() // 减少计数
process(n)
}(i)
}
wg.Wait() // 等待全部完成
}
注意事项
Go
// 错误:Add在goroutine内
go func() {
wg.Add(1) // 可能Wait已执行!
defer wg.Done()
}()
// 正确:Add在启动前
wg.Add(1)
go func() {
defer wg.Done()
}()
Add必须在goroutine启动前调用。
Once单次执行
Go
var once sync.Once
var resource *Resource
func initResource() {
once.Do(func() {
resource = &Resource{} // 只执行一次
})
}
// 多次调用只执行一次
initResource()
initResource() // 不执行
Cond条件变量
等待条件满足
Go
var mu sync.Mutex
var cond = sync.NewCond(&mu)
var ready bool
func waitReady() {
mu.Lock()
defer mu.Unlock()
for !ready {
cond.Wait() // 等待信号,释放锁
}
// 条件满足
}
func signalReady() {
mu.Lock()
ready = true
cond.Broadcast() // 唤醒所有等待者
// 或 cond.Signal() 唤醒一个
mu.Unlock()
}
Wait内部释放锁,返回前重新获取锁。
Pool对象池
Go
var pool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
func processData() {
buf := pool.Get().([]byte)
defer pool.Put(buf)
// 使用buf
// ...
}
// Pool对象可能被GC回收
// 不适合长期存储
Map并发安全map
Go
var m sync.Map
// 存储
m.Store("key", "value")
// 读取
v, ok := m.Load("key")
// 删除
m.Delete("key")
// 遍历
m.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true // 继续遍历
})
sync.Map适合读多写少场景,纯写性能不如Mutex+map。
同步原语选择
| 原语 | 用途 | 特点 |
|---|---|---|
| Mutex | 保护共享资源 | 简单互斥 |
| RWMutex | 读多写少 | 读并行 |
| WaitGroup | 等待完成 | 协调goroutine |
| Once | 单次初始化 | 懒加载 |
| Cond | 等待条件 | 状态同步 |
| Pool | 对象复用 | 减少GC |
| sync.Map | 并发map | 读多写少优 |
常见陷阱
1. 锁未释放
Go
mu.Lock()
// panic或return导致未Unlock
2. 复制锁
Go
// 错误:复制Mutex
var mu sync.Mutex
mu2 := mu // 复制!锁状态丢失
// 正确:使用指针
var mu *sync.Mutex = &sync.Mutex{}
3. Wait的for循环
Go
// 错误:Wait不在循环中
if !ready {
cond.Wait() // 可能虚假唤醒
}
// 正确:Wait在循环中
for !ready {
cond.Wait() // 处理虚假唤醒
}
要点总结
- Mutex用于互斥保护,必须defer Unlock
- RWMutex适合读多写少,RLock可并行
- WaitGroup.Add在goroutine启动前调用
- Once.Do保证只执行一次
- Cond.Wait必须在循环中检查条件
- Pool复用临时对象,不适合长期存储
- sync.Map读多写少场景性能优
- 锁不能复制,使用指针传递
📝 发现内容有误?点击此处直接编辑