Go 并发编程实战课
晁岳攀(鸟窝)
前微博技术专家,知名微服务框架 rpcx 作者
25635 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 22 讲
Go 并发编程实战课
15
15
1.0x
00:00/00:00
登录|注册

15 | 内存模型:Go如何保证并发读写的顺序?

不要自作聪明
谨慎使用保证
atomic
Once
WaitGroup
Mutex/RWMutex
Channel
goroutine
init函数
例子
在goroutine内部的保证
例子
代码执行顺序不确定
CPU指令重排和多级Cache的存在
并发环境中多goroutine读相同变量的可见性条件
思考题
总结
Go语言中保证的happens-before关系
happens-before
重排和可见性的问题
内存模型含义
Go内存模型

该思维导图由 AI 生成,仅供参考

你好,我是鸟窝。
Go 官方文档里专门介绍了 Go 的内存模型,你不要误解这里的内存模型的含义,它并不是指 Go 对象的内存分配、内存回收和内存整理的规范,它描述的是并发环境中多 goroutine 读相同变量的时候,变量的可见性条件。具体点说,就是指,在什么条件下,goroutine 在读取一个变量的值的时候,能够看到其它 goroutine 对这个变量进行的写的结果。
由于 CPU 指令重排和多级 Cache 的存在,保证多核访问同一个变量这件事儿变得非常复杂。毕竟,不同 CPU 架构(x86/amd64、ARM、Power 等)的处理方式也不一样,再加上编译器的优化也可能对指令进行重排,所以编程语言需要一个规范,来明确多线程同时访问同一个变量的可见性和顺序( Russ Cox 在麻省理工学院 6.824 分布式系统 Distributed Systems 课程 的一课,专门介绍了相关的知识)。在编程语言中,这个规范被叫做内存模型。
除了 Go,Java、C++、C、C#、Rust 等编程语言也有内存模型。为什么这些编程语言都要定义内存模型呢?在我看来,主要是两个目的。
向广大的程序员提供一种保证,以便他们在做设计和开发程序时,面对同一个数据同时被多个 goroutine 访问的情况,可以做一些串行化访问的控制,比如使用 Channel 或者 sync 包和 sync/atomic 包中的并发原语。
允许编译器和硬件对程序做一些优化。这一点其实主要是为编译器开发者提供的保证,这样可以方便他们对 Go 的编译器做优化。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言内存模型对于并发编程中的可见性和顺序问题提供了明确的规范。文章介绍了内存模型中的重排和可见性问题,以及通过happens-before关系保证多个goroutine对共享变量的顺序访问。在单个goroutine内部,程序执行顺序和代码指定的顺序一致,而多个goroutine之间的共享变量读写顺序可以通过并发原语建立happens-before关系。此外,文章还介绍了init函数的执行顺序规则,以及goroutine、Channel和Mutex/RWMutex的happens-before关系保证。这些内容有助于程序员在设计和开发程序时,处理多个goroutine同时访问同一数据的情况,同时也为编译器和硬件对程序做一些优化提供了保证。文章内容涵盖了Go语言并发编程中的重要概念和规范,对于理解并发编程和编写高效的并发程序具有重要意义。文章还提到了WaitGroup、Once和atomic等并发原语的使用规则和保证,以及对于不同情况下的适用性和注意事项。总的来说,本文通过深入讨论Go语言内存模型和并发原语的规范和保证,为读者提供了全面的并发编程指导,同时也警示读者在使用这些特性时要谨慎小心,避免出现意料之外的问题。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Go 并发编程实战课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(30)

  • 最新
  • 精选
  • NULL
    补充个go语言圣经8.4.1节的内容:在讨论并发编程时,当我们说x事件在y事件之前发生(happens before),我们并不是说x事件在时间上比y时间更早;我们要表达的意思是要保证在此之前的事件都已经完成了,例如在此之前的更新某些变量的操作已经完成,你可以放心依赖这些已完成的事件了。 当我们说x事件既不是在y事件之前发生也不是在y事件之后发生,我们就说x事件和y事件是并发的。这并不是意味着x事件和y事件就一定是同时发生的,我们只是不能确定这两个事件发生的先后顺序。

    作者回复: 👍🏻

    2021-12-29
    2
    14
  • 那时刻
    请问老师,文中例子中包P3中lib1先于lib2执行初始化,这个顺序是否是否有happen before呢?同一个package内文件初始化顺序是按照文件名字母序来执行的么?

    作者回复: 1.有happen before关系 2.没有规定,虽然实际是这样的。你肯定也不会也不应该利用这个顺序做点事情

    2020-11-13
    4
  • 蜉蝣
    老师好,我有些不明白,既然“在一个 goroutine 内部,程序的执行顺序和它们的代码指定的顺序是一样的”,那为什么还会有 “程序运行的时候,不能保证 g2 看到的 a 和 b 的赋值有先后关系”?假设 g2 看到了 b=2,说明 “b=2” 一定执行过了,而单个 goroutine 内部顺序是有保证,所以 a=1 也一定执行过了。基于这样的思考,后面所有示例我基本都理解不了……

    作者回复: g1看到的顺序和编写顺序效果上看是一致的,但是考虑的多核和乱序执行,真正运行不一定和编写顺序一样

    2020-11-21
    7
    1
  • 大漠胡萝卜
    没看明白:“第 n 次的 m.Unlock 一定 happens before 第 n+1 m.Lock 方法的返回;” 下面的代码第二次加锁可能先于第一次解锁 ```go var mu sync.Mutex var s string func foo() { s = "hello, world" mu.Unlock() } func main() { mu.Lock() go foo() mu.Lock() print(s) ```

    作者回复: 第二个lock方法的返回,不是第二个lock方法的调用

    2020-11-18
    2
    1
  • 王轲
    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. 此处两个clever都是贬义,我觉得可以翻译成“炫技”。 如果你需要研读这篇文档,才能理解你程序的行为的话,说明你程序写得太“炫技”了。不要太“炫技”。 Golang是追求直白通俗易懂的语言,memory model不是用于指导写代码的,只是一篇技术细节文档。 代码还是要写得足够简单、可读性强、就算初级或没有Golang memory model相关经验的程序员,也都能读懂,才是好代码。

    作者回复: 👍🏻

    2023-06-02归属地:上海
  • Alex
    请问老师 第一条规则和第四条规则 我可不可以认为 channel 本质上是一个先入先出(FIFO)的队列 所以接收的数据和发送的数据的顺序要一致

    作者回复: 你可以这么认为

    2023-02-05归属地:江苏
  • Alex
    第三个规则的例子写错了 var ch = make(chan int) 应该是 var ch = make(chan struct{})

    作者回复: 是的,谢谢指正

    2023-02-05归属地:江苏
  • tingting
    在这个例子中,s 的初始化(第 5 行)happens before 往 ch 中发送数据, 往 ch 发送数据 happens before 从 ch 中读取出一条数据(第 11 行),第 12 行打印 s 的值 happens after 第 11 行,所以,打印的结果肯定是初始化后的 s 的值“hello world”。 为什么另外一个goroutine 这里可以感知到“s 的初始化(第 5 行)happens before 往 ch 中发送数据”? 跟第一个例子类似,这里不是有可能指令重排吗?

    作者回复: 这是chan保证的,第一个例子没有谁可以保证

    2022-08-25归属地:北京
    2
  • tingting
    第一个例子:“可以看到,第 9 行是要打印 b 的值。需要注意的是,即使这里打印出的值是 2,但是依然可能在打印 a 的值时,打印出初始值 0,而不是 1。” 如果打印出来的是2,那是不是说明对于该次执行,b=2 happens before print(b), a=1 happens before b=2,所以打印出来的应该一定是1。

    作者回复: a=1 happens before b=2这个是推断不出来的。从另一个goroutine看不一定

    2022-08-25归属地:北京
  • Geek_a6104e
    所以执行到第 10 行的时候,sd 已经 打错字了 应该是s而不是sd

    作者回复: 谢谢,马上更新

    2022-07-31归属地:北京
收起评论
显示
设置
留言
30
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部