Go如何保证并发安全(四) Go内存模型

这是我参与11月更文挑战的第4天,活动详情查看:2021最后一次更文挑战

Go内存模型

软件(编译器)或硬件(CPU)系统可以根据其对代码的分析结果,一定程度上打乱代码的执行顺序,以达到其不可告人的目的(提高 CPU 利用率) from 曹大

  • 可见性:GO 内存模型阐明了一个goroutine对某变量的写入,如何才能保证被另一个读取该变量的goroutine监测到。
  • 事件的发成次序:程序在修改被多个goroutine同时访问的数据时必须序列化该访问。

要序列化访问,需要通过channel,或其它像sync和sync/atomic包中的同步原语来保护数据。

Happens Before(事件的发生次序)

定义

若事件e1发生在e2之前,那么我们就说e2发生在e1之后。换言之,若e1既未发生在e2之前,又未发生在e2之后,那么我们就说e1与e2是并发的。

解释

在单个goroutine中,事件发生的顺序即为程序所表达的顺序。

仅在不会改变语言规范对goroutine行为的定义时,编译器和处理起才会对读取和写入的顺序进行重新排序。

由于存在重新排序,一个goroutine监测到的执行顺序可能与另一个goroutine监测到的不同。例如,若一个goroutine执行a=1;b=2;,另一个goroutine可能监测到b的值先与a更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
diff复制代码单goroutine的情形:
-- w0 ---- r1 -- w1 ---- w2 ---- r2 ---- r3 ------>

所有 r/w 的先后顺序都是可比较的。

双Go程的情形:
-- w0 -- r1 -- r2 ---- w3 ---- w4 ---- r5 -------->
-- w1 ----- w2 -- r3 ---- r4 ---- w5 -------->

单goroutine上的事件都有先后顺序;而对于两个goroutine,情况又有所不同。即便在时间上 r1 先于 w2 发生,
但由于每条goroutine执行时长都像皮筋一样伸缩不定,因此二者在逻辑上并无先后次序。换言之,即二者并发。
对于并发的 r/w,r3 读取的结果可能是前面的 w2,也可能是上面的 w3,甚至 w4 的值;
而 r5 读取的结果,可能是 w4 的值,也能是 w1、w2、w5 的值,但不可能是 w3 的值。


双goroutine交叉同步的情形:
-- r0 -- r1 ---|------ r2 ------------|-- w5 ------>
-- w1 --- w2 --|-- r3 ---- r4 -- w4 --|------->

现在上面添加了两个同步点,即 | 处。这样的话,r3 就是后于 r1 ,先于 w5 发生的。
r2 之前的写入为 w2,但与其并发的有 w4,因此 r2 的值是不确定的:可以是 w2,也可以是 w4。
而 r4 之前的写入的是 w2,与它并发的并没有写入,因此 r4 读取的值为 w2。

同步

初始化

程序的初始化运行在单个goroutine中,但该goroutine可能会创建其它并发运行的goroutine。

若包p导入了包q,则q的init函数会在p的任何函数启动前完成。

函数main.main会在所有的init函数结束后启动。

goroutine的创建

go语句会在当前goroutine开始执行前启动新的goroutine。

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

这个不是很理解要表达的是什么意思(通过go关键字创建一个新的goroutine的动作发生在goroutine执行之前?)

1
2
3
4
5
6
7
8
9
10
go复制代码var a string

func f() {
print(a)
}

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

goroutine的销毁

goroutine无法确保在程序中的任何事件发生之前退出。(在不使用同步原语保护的情况下)

1
2
3
4
5
6
go复制代码var a string

func hello() {
go func() { a = "hello" }()
print(a)
}

例如:goroutine无法确保在打印前完成对变量a的赋值,因为对a进行赋值后并没有任何同步事件,因此无法保证被其它goroutine监测到(可以使用channel来解决)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%