Golang Memery Model 内存模型

在同一个 Goroutine 中,如果我们有下面的语句:

a = 1

b = 3

我们可以保证这几条赋值语句是按顺序执行的。但是,对于另一个 Goroutine 来说,它所观察到的顺序可能不是我们在代码里看到的顺序,比如,它可能先观察到 b = 3,然后 a = 1。至于原因可以了解一下 CPU 缓存一致性协议 MESI,以及有了MESI之后为什么还会有缓存一致性问题。

那这会造成什么问题呢?比如我们可以看一下下面这些有问题的代码:

var a, b int

func f() {

a = 1

b = 2

}

func main() {

go f()

print(a)

print(b)

}

打印出来的 a 和 b 的值可能是赋值之后的,也可能是 0

再来看另一个有问题的代码:

var a string

var done bool

func setup() {

a = "hello, world"

done = true

}

func main() {

go setup()

for !done {}

print(a)

}

我们创建了setup线程,用于对字符串a的初始化工作,初始化完成之后设置done标志为true。main函数所在的主线程中,通过for !done {}检测done变为true时,认为字符串初始化工作完成,然后进行字符串的打印工作。

但是Go语言并不保证在main函数中观测到的对done的写入操作发生在对字符串a的写入的操作之后,因此程序很可能打印一个空字符串。更糟糕的是,因为两个线程之间没有同步事件,setup线程对done的写入操作甚至无法被main线程看到(可能始终在CPU寄存器中),main函数有可能陷入死循环中。

因此,Go内存模型其实是一个概念,指定了某些条件,在这些条件下,可以保证在一个Goroutine中对一个共享变量的写入,可以被另一个Goroutine观察到。

什么是 Happens Before

就是字面意思,两个语句a = 1; b = 3只有三种情况:

  1. a = 1 happens before b = 3
  2. a = 1 happens after b = 3
  3. a = 1 and b = 3 happen concurrently

如果对一个变量的赋值操作w要保证被另一个读取操作r观察到,运用 Happens before 概念,我们可以得出需要满足如下条件:

  • w happens before r
  • 任何其他对变量的赋值操作要么happens before w,要么happens after r

下面介绍一些在Go编程中可以确定是 happens before 的语句(不全,更详细的可以参考官方文档)

init()

如果在package a中导入了package b,那么package binit() happens before package ainit()

channel

  • 对一个channel的发送操作 happens before 接收操作完成
  • 对一个channel进行close() happens before 接收到零值
  • 对一个无缓冲channel的接收操作 happens before 发送操作完成(意思就是发送会阻塞,直到被接收)
  • 带缓冲的channel也是一样,超出缓冲区的发送会阻塞

对于最开始的那几段有问题的代码,解决办法就是通过同步原语来给两个事件明确排序。可以用sync.Mutex(),也可以用“channel

推荐阅读

关于 Golang Memory Model,就推荐一篇文章,官方文章,讲的很清楚

以上是 Golang Memery Model 内存模型 的全部内容, 来源链接: utcz.com/z/264415.html

回到顶部