Go 内存模型
发表于 2022-03
并发场景下最讨厌的是对变量的读写操作隐含着违反直觉的结果。这时候,就需要语言的内存模型了。所有语言的内存模型都在讲着一件事情,什么操作才能让写变量的结果被另一个线程/协程读到。
幸好 Go 一直是一个设计的很“简单”的语言,它的内存模型需要记住的结论很少,我们很容易就写出基于 goroutine 的并发安全程序。
先解释一个经典的名词 happens-before:如果一个事件的发生在另一个事件之前,那么第一个事件的结果必须影响到第二个事件。happens-before 听上去理应如此,但由于现代处理器的多核、指令重排,编译器优化等等因素,happens-before 不是天然成立的。
接下来看一下 Go 内存模型规定了哪些操作是确定的:
- 如果包
p导入了包q,那么包q的init函数happens-before包p的任何代码。 - 所有包的
init函数happens-beforemain.main函数。 - 用
go语句启动新的goroutine,该语句本身happens-before启动的这个新goroutine。 channel上的发送happens-before该channel上的接收。- 关闭
channel的操作happens-before在channel上读到零值(零值意味着channel关闭)。 - 无缓冲
channel的接收happens-before该channel上的发送完成。 - 容量为 C 的
channel接收到第 k 个元素happens-before该channel第 k+C 个元素发送完成。 - 两个变量 n 和 m,n 出现在 m 的前面,若使用互斥锁
sync.Mutex或sync.RWMutex保护,那么变量 n 的l.Unlockhappens-before变量 m 的l.Lock()。 - 如果有多个对
once.Do(f)的调用,那么第一个调用f()函数happens-before任何其他once.Do(f)的调用返回。 sync.Cond的Broadcast和Signal调用happens-before阻塞中Wait调用。- 对于
Sync.Map,Load,LoadAndDelete和LoadOrStore都是读操作。Delete,LoadAndDelete和Store都是写操作。LoadOrStore如果loaded返回false,那也是写操作。Sync.Map的所有写操作happens-before读操作。 - 对于
Sync.Pool,Puthappens-beforeGet,Newhappens-beforeGet。 - 对于
Sync.WaitGroup,Donehappens-before任何一个阻塞中的Wait。 sync.atomic包是一些列原子操作的合集,可用于不同 goroutine 间的同步。如果原子操作 B 的观察到原子操作 A 的结果,那么 Ahappens-beforeB。一旦使用了原子操作,那么程序便拥有了一致的执行顺序。
lyyyuna 沪ICP备2025110782号-1