前言
使用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-beforeB。happens-before是用来表示两个线程中两个操作被执行的先后顺序的一种描述。
happens-bofore有三个特点:
可传递性。如果Ahappens-beforeB,Bhappens-beforeC,则有Ahappens-beforeC;
 当store操作A与load操作B发生同步时,则Ahappens-beforeB;
 happens-before一般用于描述分别位于两个线程中的操作之间的顺序。
sequenced-before
如果在单个线程内操作A发生在操作B之前,则表示为Asequenced-beforeB。这个关系是描述单个线程内两个操作之前的先后执行顺序的,与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 beforer; - 不存在其他的写操作w1 
happens afterw,且happens beforer,即这样的结构:whappens beforew1happens beforer; 



















