全部学科
Python全栈
python
NodeJS全栈
nodejs
小程序首页
📅 2026-05-13 9 分钟 ✍️ juanwangdev

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可见

关键同步点

  1. goroutine创建
Go
// 启动goroutine happens-before 它开始执行
go func() {
    // 能看到启动前的所有写入
}
  1. goroutine销毁
Go
// goroutine完成不保证happens-before任何事件
// 需要同步机制(如channel)来保证
  1. channel发送
Go
// 发送 happens-before 接收完成
ch <- data
v := <-ch  // 能看到发送前的所有写入
  1. channel关闭
Go
// 关闭 happens-before 从关闭channel接收零值
close(ch)
v := <-ch  // v为零值,能看到关闭前的写入
  1. Lock/Unlock
Go
// Unlock happens-before 后续Lock
mu.Lock()
x = 1
mu.Unlock()

mu.Lock()  // 能看到x=1
  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.Donewg.Wait返回
atomic写入atomic读取
goroutine退出无保证(需显式同步)

未建立happens-before关系的并发访问是数据竞争。

要点总结

  • happens-before定义可见性顺序
  • goroutine创建happens-before开始执行
  • channel发送happens-before接收完成
  • Unlock happens-before后续Lock
  • atomic操作保证可见性
  • 数据竞争:并发写+无同步
  • 使用-race检测数据竞争
  • 所有共享变量访问必须同步

📝 发现内容有误?点击此处直接编辑

← 上一篇 Go垃圾回收机制深度解析
下一篇 → Go性能分析工具使用(内存排查)
想查看更多题目和详细解析?
小程序提供完整的题库、模拟考试和详细解析
马上就来

长按或扫描二维码,立即体验

扫码体验小程序
马上就来
使用微信扫描二维码
立即体验完整题库