0%

go 内存模型

原文 The Go Memory Model
Go的内存模型描述的是”在一个 groutine中 对变量进行读操作能够侦测到在其他goroutine中对该变量的写操作”的条件。

Happens Before

对变量 v 的读 read r 要观测到 write w 对变量 v 的写,需要同时满足:

  • r 不发生在 w 之前
  • 没有其他的 w’ 对变量 v 的写发生在 w 之后, r 之前.

为了保证 read 观察到唯一一个 write w.必须同时满足:

  • w 发生在 r之前
  • 其他 write 发生在 w 之前或者 r 之后
    这对条件比前一对更严格,要求没有其他的 write 没有与 w 或者 r 并发执行.

在单个 goroutine 中没有并发,是满足这两个条件的,保证 r 能看到 w 对 v 的写入.在多个 goroutine 共享变量 v 时,必须使用同步事件
满足 happens-before 条件确保 read 能读取到期望的 writes.

同步

goroutine 创建

The go statement that starts a new goroutine happens before the goroutine’s execution begins

1
2
3
4
5
6
7
8
9
10
11
12
package main
var a string

func f(){
print(a)
}

func hello(){
a = "hello world"
go f()
}

调用 hello 将会打印 “hello world”,可能发生在 hello return 之前或者之后.

channel 通信

A send on a channel happens before the corresponding receive from that channel completes. 在一个channel中,发送 happens-before 相应的接收完成之前.
The closing of a channel happens before a receive that returns a zero value because the channel is closed. 关闭一个 channel happens-before 接收返回的 0 值之前.为 0 值是因为 channel 被关闭.

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
var c = make(chan int,10)
var a = ""
func f(){
a = "hello world"
c <- 1 // 先发生,可以替换成 close(c)
}
func main(){
go f()
<- c
print(a)
}

A receive from an unbuffered channel happens before the send on that channel completes. 无缓冲 channel 的接收 happens-before 在发送完成之前.

1
2
3
4
5
6
7
8
9
10
11
12
13
package main
var c = make(chan int,0)
var a = ""
func f(){
a = "hello world"
c <- 1 // 先发生,可以替换成 close(c)
}
func main(){
go f()
<- c
print(a)
}

The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes. 容量为 C 的 channel 第 k 次接收 happens-before k+C 次发送完成之前.无缓存通道就是C 为 0 时的特殊情况.

不正确的同步

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var a string
var done bool

func setup() {
a = "hello, world"
done = true
}

func main() {
go setup()
for !done {
}
print(a)
}

对于主线程来说, setup 里面的执行顺序是不一致的, done = true 可能发生在 a 赋值之前,更糟糕的是, setup goroutine 中对 done 的修改,对
主线程来说可能是不可见的,将导致主线程死循环.

解决同步问题的思路:使用显示的同步机制.