Go并发内存模型详解
理解并发内存模型是正确编写并发程序的基础。
内存模型核心问题
多goroutine并发执行时,变量的读写顺序如何保证可见?
Go
var x, y int
go func() {
x = 1
y = 2
}()
// 主goroutine看到的是什么顺序?
fmt.Println(y, x) // 可能0,0 或 2,0 或 2,1?
Happens-Before规则
Go内存模型通过happens-before规则保证可见性。
基本规则
Go
如果事件A happens-before 事件B
则A的结果对B可见
关键同步点
- goroutine创建
Go
// 启动goroutine happens-before 它开始执行
go func() {
// 能看到启动前的所有写入
}
- goroutine销毁
Go
// goroutine完成不保证happens-before任何事件
// 需要同步机制(如channel)来保证
- channel发送
Go
// 发送 happens-before 接收完成
ch <- data
v := <-ch // 能看到发送前的所有写入
- channel关闭
Go
// 关闭 happens-before 从关闭channel接收零值
close(ch)
v := <-ch // v为零值,能看到关闭前的写入
- Lock/Unlock
Go
// Unlock happens-before 后续Lock
mu.Lock()
x = 1
mu.Unlock()
mu.Lock() // 能看到x=1
- atomic操作
Go
// atomic写入 happens-before atomic读取
atomic.StoreInt64(&x, 1)
v := atomic.LoadInt64(&x) // 能看到x=1
内存屏障
Go在关键同步点插入内存屏障。
Go
// 写屏障:确保之前写入完成
x = 1
y = 2
// 内存屏障保证x=1先于y=2可见
// 读屏障:确保读取最新值
v1 := atomic.LoadInt64(&x)
v2 := atomic.LoadInt64(&y) // 能看到屏障前的写入
同步原语语义
Channel
Go
// 无缓冲channel:同步语义强
ch := make(chan int)
go func() {
ch <- 1 // 等待接收
}()
v := <-ch // 等待发送,保证happens-before
Mutex
Go
var mu sync.Mutex
var x int
// goroutine A
mu.Lock()
x = 1
mu.Unlock() // happens-before goroutine B的Lock
// goroutine B
mu.Lock() // 能看到x=1
fmt.Println(x)
mu.Unlock()
WaitGroup
Go
var wg sync.WaitGroup
var data int
wg.Add(1)
go func() {
data = 42
wg.Done() // happens-before Wait返回
}()
wg.Wait() // 能看到data=42
fmt.Println(data)
数据竞争与避免
数据竞争定义
Go
// 数据竞争:两个goroutine并发访问同一变量
// 至少一个写,且无同步
var x int
go func() {
x = 1 // 写
}()
fmt.Println(x) // 读(无同步)→ 数据竞争!
避免数据竞争
Bash
// 方式1:使用Mutex
var mu sync.Mutex
var x int
go func() {
mu.Lock()
x = 1
mu.Unlock()
}()
mu.Lock()
fmt.Println(x)
mu.Unlock()
// 方式2:使用channel
ch := make(chan int)
go func() {
ch <- 1 // 发送
}()
x := <-ch // 接收(同步)
// 方式3:使用atomic
var x int64
go func() {
atomic.StoreInt64(&x, 1)
}()
v := atomic.LoadInt64(&x)
检测数据竞争
text
# 运行时检测
go test -race main.go
# 或运行程序
go run -race main.go
输出示例:
text
==================
WARNING: DATA RACE
Write at 0x... by goroutine 7:
main.foo()
Previous read at 0x... by goroutine 6:
==================
happens-before关系表
| 同步操作 | Happens-Before关系 |
|---|---|
| go f() | f开始执行 |
| ch发送 | ch接收完成 |
| ch关闭 | 从关闭ch接收零值 |
| Unlock | 后续Lock |
| wg.Done | wg.Wait返回 |
| atomic写入 | atomic读取 |
| goroutine退出 | 无保证(需显式同步) |
未建立happens-before关系的并发访问是数据竞争。
要点总结
- happens-before定义可见性顺序
- goroutine创建happens-before开始执行
- channel发送happens-before接收完成
- Unlock happens-before后续Lock
- atomic操作保证可见性
- 数据竞争:并发写+无同步
- 使用-race检测数据竞争
- 所有共享变量访问必须同步
📝 发现内容有误?点击此处直接编辑