"Do not communicate by sharing memory; instead, share memory by communicating" 这句话在讲 Golang 并发的好多文章里看到过,仿佛是一个格言一般。然而对于它的真正含义一直不是很清楚。
最近看到了如下的代码,才对这句话有所思考和理解。
摘自 https://golang.org/ref/mem 的一段 错误 的并发代码:
var a string
var done bool
func setup() {
a = "hello, world"
done = true
}
func main() {
go setup()
for !done {
}
print(a)
}
这段代码是典型的 communicate by sharing memory。而官网说到这段错误的代码可能的结局有两种不能接受:
-
print(a)
打印出来的是空字符串。 -
main
函数陷入死循环。
先来解释一下1:Go的内存模型里说到 “happen before” 这样一条原则,也讲了很多种遵循这条原则的操作,比如在一个 goroutine 中如果对一个变量的写w和读r,如果w在代码上位于r的前面,则 w 先于 r 发生。而这里对 a 的写操作 和 对 done 的写操作并不属于 happen before 的范围。也就是说,它们有可能同时发生 甚至 顺序颠倒。所以在 检测到 done 为 true 时,a 还未赋值的情况是有可能发生的。
再来看2:如果在main
函数的一开始或者在init
函数中通过runtime.GOMAXPROCS(1)
将并发数设置为1,那么setup
永远不会执行,main
将会陷入死循环。即使 runtime.GOMAXPROCS>1,依然存在 done 一直为false的可能性:如果 setup 所在 goroutine 中对 done 的写入一直不flush到内存(在寄存器或cache),内存中的done就会一直为false。
官网也讲到了 Go 的各种并发元语是如何满足 happens-before 原则的。思考一下,上述例子在 GOMAXPROCS = 1 时是无法正常执行的,那么那些正确的并发程序在 GOMAXPROCS = 1 时表现如何呢?所谓正确的并发程序,goroutine之间应当是通过并发元语来沟通协作的,那么他们之间必然有某种 happen-before 的约束存在,happen-before 的实质作用其实是将两个goroutine的某一部分串行起来,使用并发原语,也就是为了将需要串行的部分串行起来,这样即使是 GOMAXPROCS = 1,多个goroutine 也是能够正确协作的。
所以对于 “Do not communicate by sharing memory; instead, share memory by communicating” 这句话,我认为 share memory by communicating 的 communicating 不光是指 channel,Go 的并发元语都是 communicating,Mutex,Once,atomic, channel 等都是 goroutine 之间沟通的正确方式。
网友评论