前言

使用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