go内存模型描述的是
在一个groutine中对变量进行读操作能够侦测到在其他goroutine中对该变量的写操作"的条件
高级编程语言所支持的运算往往不是原子化的,整个并发编程的世界里一切都是不确定的,我们不知道每次读取的变量到底是不是及时、准确的数据。所以提出了happens-befor规则
happens-befor--不是时序关系
当成一种特殊的运算:如果A happens-before B,那么A操作对内存的影响 将对执行B的线程(且执行B之前)可见。
-
作用:帮助我们厘清两个并发读写之间的关系
-
想让某次读取r准确观察到某次写入w,要满足:
-
w happens befor r
-
对变量的其它写入w1,要么w1
happens-beforew,要么rhappens-beforew1;简单理解就是没有其它写入覆盖这次写入;
-
go中的happens befor
-
在同一个gorutine里,写在前面的代码happens befor后面的代码
-
happens-before关系都是可传递的:如果A happens-before B,同时B happens-before C,那么A happens-before C
-
关于channel的happens-before在Go的内存模型中提到了三种情况:
- 对一个channel的发送操作 happens-before 相应channel的接收操作完成
- 关闭一个channel happens-before 从该Channel接收到最后的返回值0
- 不带缓冲的channel的接收操作 happens-before 相应channel的发送操作完成
package main
import (
"fmt"
)
var c = make(chan int)
var a string
func f() {
a = "hello, world" //1
<-c //2
}
func main() {
go f() //3
c <- 0 //4
fmt.Print(a) //5
}
在这里写入变量a的操作(1)happen before 从通道读取数据完毕的操作(2),而从通道读取数据的操作 happen before 向通道写入数据完毕的操作(4),而步骤(4) happen before 打印输出步骤(5)。
同步
初始化
- init方法,执行某些初始化逻辑。在main之前,在一个gorutine里执行所有init方法。关于
init方法有两条happens-before规则:
1.a 包导入了 b包,此时b包的
init方法happens-beforea包的所有代码;
2.所有init方法happens-beforemain方法;
创建
- 启动一个新的goroutine的动作 happen before 该新goroutine的运行
销毁
- 一个gorutine的销毁并不能确保happen befor程序中的任何事件
var a string
func hello() {
go func() { a = "hello" }()
print(a)
}
如上代码 goroutine内对变量a的赋值并没有加任何同步措施,所以并能不保证hello函数所在的goroutine对变量a的赋值可见。如果要确保一个goroutine对变量的修改对其他goroutine可见,必须使用一定的同步机制,比如锁、通道来建立对同一个变量读写的偏序关系。
通道通信
- 对一个channel的发送操作 happens-before 相应channel的接收操作完成
- 关闭一个channel happens-before 从该Channel接收到最后的返回值0(关闭通道后会向通道发送一个0值)
- 不带缓冲的channel的接收操作 happens-before 相应channel的发送操作完成
锁
sync实现了互斥锁sync.Mutex和读写锁sync.RWMutex
- 调用n次 l.Unlock() 操作 happen before 调用m次l.Lock()操作返回,其中n<m