使用go必然会使用到协程以及其他的并发操作,初期学习的时候,经常在启动协程时操作变量出现问题,要么就是变量没更新,要么就是各种崩溃,或者vscode报告警之类的,于是浅看了一下Go的内存模型,也了解到Happens Before的概念,这里记录一下;
原文链接:go内存模型,可能要挂梯子
正文原文开篇的建议部分很有意思:
If you must read the rest of this document to understand the behavior of your program, you are being too clever.
Don’t be clever.
其实是建议大家好好看看内存模型的详述内容,不要自作聪明;
goroutine executions
- 操作的类型,表明它是一个普通的数据读取,普通的数据写入,还是一个同步操作,如原子数据访问,互斥操作,或通道操作;
- 在代码中的位置;
- 被访问的内存位置或变量;
- 操作读取或写入的值;
goroutine executions
关键概念
为了便于更好的理解后面的内容,我们需要理解几种关系术语:
happens-before(先于发生):
happens-beforehappens-before
happens-bofore
happens-beforehappens-beforehappens-beforehappens-beforehappens-before
sequenced-before
sequenced-beforehappens-before
sequenced-beforehappences-beforesequenced-beforehappences-beforesequenced-beforehappences-before
数据竞争
首先,对数据竞争进行定义,数据竞争对内存位置的写入与对同一位置的另一个读或写同时发生,除非所涉及的所有访问都是由sync/atomic包提供的操作,理解来说就是操作是否是数据安全的。
以下模拟了一个发生数据竞争的场景:
func main() {
var a int
go func() {
a = 2
fmt.Println("goroutine: ", a)
}()
go func() {
a = 3
fmt.Println("goroutine: ", a)
}()
a = 10
time.Sleep(1 * time.Second)
}
启动2个协程,同时修改a的值,测试运行,加上-race参数,将会打印数据竞争的发生情况:
Happens Before
Happens Before也叫先行发生,这也是Go中的读写操作要求,先行发生是在 Go 程序的内存操作中局部的执行顺序,既然有Happens Before那么就有Happens After,假设有两个事件e1和e2,有以下概念:
- e1发生于e2之前:e1 Happens Before 于 e2
- e1发生于e2之后:e1 Happens After 于 e2
- e1既不发生于e2之前,也不发生于之后:e1,e2同时发生
几个特征
特征一
sequenced before
一个Go程序运行会被抽象为一组goroutine执行,并伴随一个Map W;W中会指定每个读类型操作的来源写操作
特征二
synchronized before
特征三
对于一个普通操作(非同步的)数据R,内存地址为X,满足以下条件时对R的写操作w对读操作r可见:
happens beforehappens afterhappens beforehappens beforehappens before