美文网首页
go 的内存模型

go 的内存模型

作者: wayyyy | 来源:发表于2023-02-18 01:01 被阅读0次

官方介绍在 The Go Memory Model

首先来看段代码:

var a string
var done bool

func setup() {
    a = "hello, world"
    done = true
}

func main() {
    go setup()
    for !done {
    }
    print(a)
}

看起来,a一定会输出为"hello world",但实际上这个程序也可能打印一个空字符串,因为不能保证在 main 中,观察对 done 的写入就意味着能观察到对 a 的写入:

  1. 编译器可能把无关的代码乱序以提高效率
  2. CPU 可能会产生执行上的乱序(比如 Intel 的 Store-Load 乱序)

下面是go的内存模型中带有同步语义的保证:

初始化
创建 goruntine

启动新 goroutine 的 go 语句在 goroutine 开始之前执行

var a string

func f() {
    print(a)
}

func hello() {
    a = "hello, world"
    go f()
}

可以确定的是 a = "hello world"

销毁 goruntine
var a string

func hello() {
    go func() { a = "hello" }()
    print(a)
}
channel 通信

对于有缓冲的channel,send(生产者)先于 receive(消费者)完成

var c = make(chan int, 10)
var a string

func f() {
    a = "hello, world"
    c <- 0  // send(生产者)
}

func main() {
    go f()
    <-c  // receive(消费者)
    print(a)
}

可以确定稳定打印出"hello, world",执行顺序:a = "hello, world" -> send -> receive -> print

对于无缓冲的channel,receive(消费者)先于 send(生产者)完成

var c = make(chan int)
var a string

func f() {
    a = "hello, world"
    <-c  // receive(消费者)
}

func main() {
    go f()
    c <- 0  // send(生产者)
    print(a)
}

对比可以发现,channel变成非缓冲,同时send和receive的位置发生了转换,仍然可以确定稳定打印出"hello, world",执行顺序:a = "hello, world" -> receive -> send -> print

Lock
var l sync.Mutex
var a string

func f() {
    a = "hello, world"
    l.Unlock()
}

func main() {
    l.Lock()
    go f()
    l.Lock()
    print(a)
}
Once

TODO


不正确的同步
var a, b int

func f() {
    a = 1
    b = 2
}

func g() {
    print(b)
    print(a)
}

func main() {
    go f()
    g()
}

这个和最开头的例子差不多,看起来我们会认为会有2个前提:a = 1一定会先于b = 2执行,print(b)一定会先于print(a)执行。
所以排列组合如下:

1 2 3 4 结果
a=1 print(a) b=2 print(b) 1 2
a=1 b=2 print(a) print(b) 1 2
print(b) print(a) a=1 b=2 0 0
print(b) a=1 print(a) b=2 0 1

但实际上,我们可能得出的结果是:2 0,也就是说上面2个前提是不存在的,所以老老实实的用其他同步机制来保证正确的结果。

不正确的编译

TODO


go 官方的建议

所以,综上,最终 go 官方的建议:

通过多个goruntine同时访问来修改数据的行为,必须将这些访问序列化,为了访问序列化来保护数据,可以通过channel操作和其他同步原语(sync&sync/atomic)来实现。

相关文章

  • go 内存模型简要说明

    go 内存模型 大体上来说go的内存是先申请一大片内存,然后将内存分为各个小的span来管理,因为每个go对象有对...

  • go的内存模型

    Go内存模型 介绍 Go的内存模型指定了一个条件,在该条件下,可以保证在一个goroutine中读取便利,以观察通...

  • Go——内存模型

    什么是内存模型 The Go memory model specifies the conditions unde...

  • Go的内存模型

    介绍 如何保证在一个goroutine中看到在另一个goroutine修改的变量的值,这篇文章进行了详细说明。 建...

  • Go 的内存模型

    介绍 Go 的内存模型是可以让多个 goroutine 共享数据的,但指定了条件,在这种条件下,保证一个 goro...

  • Go 内存模型(The Go Memory Model)

    原文链接:https://golang.org/ref/mem,2014.05.31日的版本。自翻,仅供参考。 I...

  • go-map源码简单分析(map遍历为什么时随机的)

    GO 中map的底层是如何实现的 首先Go 语言采用的是哈希查找表,并且使用链表解决哈希冲突。 GO的内存模型 先...

  • golang并发总结

    golang并发模型 go在语言层面提供了内置的并发支持 不要通过共享内存来通信,而应该通过通信来共享内存 并发与...

  • 白话Go内存模型&Happen-Before

    来自公#众#号:Gopher指北[https://isites.github.io/] Go内存模型明确指出,一个...

  • Flink JobManager | TaskManager内存

    Flink内存模型分析 JobManager内存模型 TaskManager内存模型 内存模型分析 Flink使用...

网友评论

      本文标题:go 的内存模型

      本文链接:https://www.haomeiwen.com/subject/xbtinrtx.html