Go语言内存模型

go

The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine.

Go语言内存模型指定了一个goroutine可以保证读取到另一个goroutine对于某个变量的修改的条件。

这篇文章基本上是对官方文档的简化的翻译,是为了加深我对golang内存模型的理解。

为什么需要内存模型

我的理解是内存模型的出现是为了解决两个问题。第一个是编译器会对代码进行优化,进行指令的重排序。而对读写操作的重排序,在多线程程序中会引发竞态条件1。因此我们需要一种规则,来限制指令的重排序不会改变程序的语义。第二个问题是,在多核计算机中运行的多线程程序,往往一个线程会映射到一个核中进行计算。现代CPU中,为了提高计算速度,对于变量的读写不是直接在内存中进行,而是先从内存中读取到计算核对应的缓存,在缓存计算好后再写回内存。因此我们需要一种规则,来保证各个线程(计算核)对于内存变量的修改是原子的。

先行原则(Happen-Before)

先行原则指定了一种偏序关系:事件A先于事件B发生。可以理解为内存模型通过提供同步操作(语法和库层面),来满足一定的先行原则,从而实现多线程(多goroutine程序)按设计的顺序推进。

Go语言包含以下的先行原则:

init

被导入包的init函数执行完成先于当前包的init函数开始执行。所有init函数执行完成先于main.main开始执行。

goroutine

go关键字创建goroutine先行发生于goroutine开始执行。

channel

  • 向channel发送先行发生于对应的从channel接收完成。
  • 关闭channel先行发生于从关闭的channel接收到零值。
  • 从不带buffer的channel接收先行发生于对应的向channel发送完成。
  • 对于容量为n的带buffer的channel,第k次从该channel的接收先行发生于第k+n次向该channel发送完成。

locks

对于sync.Mutex和sync.RWMutex,第n次Unlock先行发生于第m次Lock(n < m)。

对于sync.RWMutex,发生在第n次Unlock后的RLock,其对应的RUnlock先行发生于第n+1次Unlock。

once

对于once.Do(f),f()函数的执行完成,先行发生于任意一次once.Do()执行完成。


  1. 程序的输出依赖于多个线程的推进顺序