前言
使用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执行创建的(goroutine executions
);一个内存操作由4种信息定义:
- 操作的类型,表明它是一个普通的数据读取,普通的数据写入,还是一个同步操作,如原子数据访问,互斥操作,或通道操作;
- 在代码中的位置;
- 被访问的内存位置或变量;
- 操作读取或写入的值;
goroutine executions
为由单个goroutine执行的一组内存操作,我的理解就是一个协程中发生的内存操作;
关键概念
为了便于更好的理解后面的内容,我们需要理解几种关系术语:
happens-before(先于发生):
当线程1中的操作A先执行,而线程2中的操作B后执行时,A就happens-before
B。happens-before
是用来表示两个线程中两个操作被执行的先后顺序的一种描述。
happens-bofore
有三个特点:
可传递性。如果Ahappens-before
B,Bhappens-before
C,则有Ahappens-before
C;
当store操作A与load操作B发生同步时,则Ahappens-before
B;
happens-before
一般用于描述分别位于两个线程中的操作之间的顺序。
sequenced-before
如果在单个线程内操作A发生在操作B之前,则表示为Asequenced-before
B。这个关系是描述单个线程内两个操作之前的先后执行顺序的,与happens-before
是相对的。
此外,sequenced-before也具有可传递性,并且sequenced-before
与happences-before
之间也具有可传递性:如果线程1中操作Asequenced-before
操作B,而操作Bhappences-before
线程2中的操作C,操作Csequenced-before
线程2中的操作D,则有操作Ahappences-before
操作D。
参考文章:
聊聊内存模型和内存序
C++11内存模型完全解读-从硬件层面和内存模型规则层面双重解读
数据竞争
首先,对数据竞争进行定义,数据竞争对内存位置的写入与对同一位置的另一个读或写同时发生,除非所涉及的所有访问都是由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中会指定每个读类型操作的来源写操作
特征二
如果一个同步的读类内存操作r观察到一个同步的写类内存操作w,那么w synchronized before
r
特征三
对于一个普通操作(非同步的)数据R,内存地址为X,满足以下条件时对R的写操作w对读操作r可见:
- w
happens before
r; - 不存在其他的写操作w1
happens after
w,且happens before
r,即这样的结构:whappens before
w1happens before
r;