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
只有三种情况:
a = 1
happens beforeb = 3
a = 1
happens afterb = 3
a = 1
andb = 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 b
的init()
happens before package a
的init()
channel
- 对一个channel的发送操作 happens before 接收操作完成
- 对一个channel进行
close()
happens before 接收到零值 - 对一个无缓冲channel的接收操作 happens before 发送操作完成(意思就是发送会阻塞,直到被接收)
- 带缓冲的channel也是一样,超出缓冲区的发送会阻塞
对于最开始的那几段有问题的代码,解决办法就是通过同步原语来给两个事件明确排序。可以用sync.Mutex()
,也可以用``channel
推荐阅读
关于 Golang Memory Model,就推荐一篇文章,官方文章,讲的很清楚
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
上一篇: Go 内存管理
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论