题目
并发环境下如何安全读写共享变量
信息
- 类型:问答
- 难度:⭐
考点
并发安全,可见性,同步机制
快速回答
在Go并发编程中安全读写共享变量的核心方法:
- 使用
sync.Mutex互斥锁保护共享资源 - 通过
sync/atomic包进行原子操作 - 遵循『不要通过共享内存来通信,而应通过通信来共享内存』原则
原理说明
Go内存模型规定:当多个goroutine并发访问共享变量时,如果没有正确的同步机制:
- 可能发生数据竞争(Data Race),导致未定义行为
- 一个goroutine的写入可能对其他goroutine不可见(可见性问题)
- 非原子操作可能被中间打断(如64位整数的读写)
代码示例
错误示例(存在数据竞争):
package main
import "fmt"
func main() {
var counter int
for i := 0; i < 1000; i++ {
go func() { counter++ }() // 并发写,存在竞争
}
fmt.Println(counter) // 结果不确定
}正确方案1:使用互斥锁
package main
import (
"fmt"
"sync"
)
func main() {
var (
counter int
mu sync.Mutex
)
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
mu.Lock()
defer mu.Unlock()
counter++
wg.Done()
}()
}
wg.Wait()
fmt.Println(counter) // 稳定输出1000
}正确方案2:使用原子操作
package main
import (
"fmt"
"sync/atomic"
"sync"
)
func main() {
var counter int64
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() {
atomic.AddInt64(&counter, 1)
wg.Done()
}()
}
wg.Wait()
fmt.Println(counter) // 稳定输出1000
}最佳实践
- 最小化锁范围:锁的临界区应尽可能小
- 使用defer解锁:避免忘记解锁导致死锁
- 原子操作优先:简单计数场景首选atomic包
- 通道替代:复杂场景考虑用channel传递数据所有权
常见错误
- 忘记解锁互斥锁(导致死锁)
- 复制已使用的互斥锁(sync.Mutex禁止复制)
- 误以为单个机器字读写是原子的(32位系统上int64非原子)
- 未使用WaitGroup等待goroutine完成
扩展知识
- 可见性保证:sync包操作隐含内存屏障,确保修改对其他goroutine可见
- 原子操作限制:atomic仅适用于基本类型,结构体需用mutex
- 竞争检测:运行时加
-race标志检测数据竞争(go run -race main.go) - Once原理:sync.Once通过原子操作+互斥锁保证单次执行