针对场景
Go的内存模型就是向广大程序员提供一种保证,保证多个goroutine访问同一个变量时,按照预定的顺序串行执行
重排、可见性问题
由于编译器、CPU的指令重排的存在,可能导致程序没有按照我们写的顺序执行出现可见性问题,此时就需要通过Go的内存屏障来保证执行顺序
Go内存屏障中有一个很重要的概念:happens-before,如果某些操作满足happens-before关系,那么就可以保证这些操作的先后顺序
import "time"
var a, b int
func write() {
a = 1
b = 2
}
func read() {
println(b)
println(a)
}
func main() {
go write()
go read()
time.Sleep(1 * time.Second)
}
可见性问题:b打印为2时,a打印可能是0,因为这个程序没办法保证a=1在println(a)前
happens-before
a、b两个操作如果满足a happens-before b,那么a操作一定在b操作前执行
如果要保证对变量的读操作r,能够观察到一个对变量的写操作w,那么要保证两点:
- w happens-before v
- 其他的写操作(w1, w2, w3…)要么happens-before w,要么happens-after r
Go提供的happens-before
单goroutine
单个goroutine内部,代码编写的顺序和happens-before关系一致
init函数
单个包的happens-before
- 包级别变量的初始化happens-before init函数
- 包级别变量按照声明的顺序初始化,除非初始化时依赖后面声明的变量
每个文件只能有一个init函数,init函数按照文件名顺序执行
多个包的happens-before
- 如果包p import 包q,那么包q的init函数 happens-before包p的任何初始化代码

go关键字
go语句的执行 happens-before goroutine中代码的执行
var x string
func hello() {
x = "hello gopher"
go func() {
println(x)
}()
}
x的写 happens-before go语句执行,go语句执行 happens-before println的执行,所以打印出来的一定是"hello gopher"
Channel
Channel提供的happens-before有四条规则:
对于buffered chan,第N次send happens-before 第N次recv的完成
对于unbuffered chan,recv happens-before send的完成
close一个Chan happens-before 从这个Chan读出一个零值
如果chan的容量是m,那么第n个recv happens-before 第n+m个send的完成
Mutex/RWMutex
Mutex、RWMutex提供的happens-before有三条原则:
第n次Unlock happens-before 第n+1次Lock
对于RWMutex,如果第n次的Lock调用返回了,那么第n次Unlock happens-before 任何一个RLock方法调用的返回
对于RWMutex,如果第n次的RLock调用返回了,那么第k次(k <= n)的RUnlock happens-before 任何一个Lock方法调用的返回
WaitGroup
对于一个WaitGroup,如果T0时刻计数值不为0,那么T0时刻后的wg.Add、wg.Done happens-before Wait方法调用的返回
Once
对于once.Do(f)的调用,f函数的单次调用 happens-before 任意次调用Do方法返回