go内存模型

128 阅读2分钟

go内存模型描述的是

在一个groutine中对变量进行读操作能够侦测到在其他goroutine中对该变量的写操作"的条件

高级编程语言所支持的运算往往不是原子化的,整个并发编程的世界里一切都是不确定的,我们不知道每次读取的变量到底是不是及时、准确的数据。所以提出了happens-befor规则

happens-befor--不是时序关系

当成一种特殊的运算:如果A happens-before B,那么A操作对内存的影响 将对执行B的线程(且执行B之前)可见。

  • 作用:帮助我们厘清两个并发读写之间的关系

  • 想让某次读取r准确观察到某次写入w,要满足:

    1. w happens befor r

    2. 对变量的其它写入w1,要么w1 happens-before w,要么r happens-before w1;简单理解就是没有其它写入覆盖这次写入;

go中的happens befor

  • 在同一个gorutine里,写在前面的代码happens befor后面的代码

  • happens-before关系都是可传递的:如果A happens-before B,同时B happens-before C,那么A happens-before C

  • 关于channel的happens-before在Go的内存模型中提到了三种情况:

  1. 对一个channel的发送操作 happens-before 相应channel的接收操作完成
  2. 关闭一个channel happens-before 从该Channel接收到最后的返回值0
  3. 不带缓冲的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-before a包的所有代码;
2.所有init方法happens-before main方法;

创建

  • 启动一个新的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可见,必须使用一定的同步机制,比如锁、通道来建立对同一个变量读写的偏序关系。

通道通信

  1. 对一个channel的发送操作 happens-before 相应channel的接收操作完成
  2. 关闭一个channel happens-before 从该Channel接收到最后的返回值0(关闭通道后会向通道发送一个0值)
  3. 不带缓冲的channel的接收操作 happens-before 相应channel的发送操作完成

sync实现了互斥锁sync.Mutex和读写锁sync.RWMutex

  • 调用n次 l.Unlock() 操作 happen before 调用m次l.Lock()操作返回,其中n<m

image.png