在同一个 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观察到。
就是字面意思,两个语句a = 1; b = 3只有三种情况:
如果对一个变量的赋值操作w要保证被另一个读取操作r观察到,运用 Happens before 概念,我们可以得出需要满足如下条件:
下面介绍一些在Go编程中可以确定是 happens before 的语句(不全,更详细的可以参考官方文档)
如果在package a中导入了package b,那么package b的init() happens before package a的init()
对于最开始的那几段有问题的代码,解决办法就是通过同步原语来给两个事件明确排序。可以用sync.Mutex(),也可以用``channel
关于 Golang Memory Model,就推荐一篇文章,官方文章,讲的很清楚