Tony Bai · Go 语言第一课
Tony Bai
资深架构师,tonybai.com 博主
21492 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 59 讲
开篇词 (1讲)
结束语 (1讲)
Tony Bai · Go 语言第一课
15
15
1.0x
00:00/00:00
登录|注册

34|并发:如何使用共享变量?

atomic vs sync.RWMutex
针对自定义类型
针对整型变量
适用场景
用法
使用原则
用法
不应复制使用过的同步类型实例
sync.Cond
sync.RWMutex
sync.Mutex
结构体状态同步
高性能临界区同步
“不要通过共享内存来通信,应该通过通信来共享内存”
channel
Goroutine
死锁模拟程序
死锁产生条件
性能敏感场景下的选择
sync包和atomic包提供低级同步原语
Go支持基于共享内存并发模型
适用场景
性能对比
atomic包能力
示例改造
使用场景
传统条件变量概念
性能基准测试
RWMutex
Mutex
注意事项
提供的原语
应用场景
Rob Pike名言
基于CSP并发模型
思考题
小结
原子操作(atomic operations)
条件变量(sync.Cond)
互斥锁(Mutex)与读写锁(RWMutex)
sync包低级同步原语
Go并发实现方案
Go并发:如何使用共享变量?

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

你好,我是 Tony Bai。
在前面的讲解中,我们学习了 Go 的并发实现方案,知道了 Go 基于 Tony Hoare 的 CSP 并发模型理论,实现了 Goroutine、channel 等并发原语。
并且,Go 语言之父 Rob Pike 还有一句经典名言:“不要通过共享内存来通信,应该通过通信来共享内存(Don’t communicate by sharing memory, share memory by communicating)”,这就奠定了 Go 应用并发设计的主流风格:使用 channel 进行不同 Goroutine 间的通信
不过,Go 也并没有彻底放弃基于共享内存的并发模型,而是在提供 CSP 并发模型原语的同时,还通过标准库的 sync 包,提供了针对传统的、基于共享内存并发模型的低级同步原语,包括:互斥锁(sync.Mutex)、读写锁(sync.RWMutex)、条件变量(sync.Cond)等,并通过 atomic 包提供了原子操作原语等等。显然,基于共享内存的并发模型在 Go 语言中依然有它的“用武之地”。
所以,在并发的最后一讲,我们就围绕 sync 包中的几个同步结构与对应的方法,聊聊基于共享内存的并发模型在 Go 中的应用。
我们先来看看在哪些场景下,我们需要用到 sync 包提供的低级同步原语。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

Go语言并发编程中的共享变量使用及同步原语选择是本文的重点。文章首先介绍了Go语言的并发设计基于CSP并发模型,通过Goroutine和channel进行通信来共享内存。同时,也详细分析了sync包提供的低级同步原语,如互斥锁和读写锁,并通过性能基准测试展示了其在高性能临界区同步机制场景下的优势。此外,文章还介绍了条件变量sync.Cond的概念及在实际场景中的应用,强调了其在“等待某个条件成立”的场景下的作用。通过性能对比和注意事项的介绍,为读者提供了实用的技术指导和建议。总的来说,本文为读者提供了关于Go并发编程中共享变量使用的全面指南,帮助读者快速了解并掌握相关技术特点。文章还通过性能测试结果和对比,指出了atomic包更适合一些对性能十分敏感、并发量较大且读多写少的场合。最后,文章提出了思考题,引发读者对死锁问题的思考。文章内容丰富,涵盖了Go语言并发编程中的关键技术点,对读者具有很高的参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Tony Bai · Go 语言第一课》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(21)

  • 最新
  • 精选
  • 步比天下
    老师,我想问个低级的问题,什么是临界区啊,不太懂

    作者回复: 临界区其实就是一个代码片段。但这个代码片段中有共享的数据,这些数据不支持多个goroutine的并发访问,只能通过像channel、锁等机制同步各个goroutine的访问。同一时间,只能有一个goroutine访问这段代码,修改或读取这段代码所共享的数据。

    2022-01-16
    5
    15
  • 罗杰
    连续三节课都是需要花费 30 分钟才能阅读完,即使是复习也要超过 20 分钟,这几节的内容真的好充实。

    作者回复: 👍

    2022-01-14
    6
    11
  • Calvin
    思考题: 死锁产生的 4 个必要条件:1) 不可剥夺;2) 请求与保持;3) 循环等待;4) 互斥。 以下模块程序会发生死锁,报 fatal error: all goroutines are asleep - deadlock! func op1(mu1, mu2 *sync.Mutex, wg *sync.WaitGroup) { mu1.Lock() time.Sleep(1 * time.Second) mu2.Lock() println("op1: do something...") mu2.Unlock() mu1.Unlock() wg.Done() } func op2(mu1, mu2 *sync.Mutex, wg *sync.WaitGroup) { mu2.Lock() time.Sleep(2 * time.Second) mu1.Lock() println("op2: do something...") mu1.Unlock() mu2.Unlock() wg.Done() } func TestDeadLock(t *testing.T) { var mu1 sync.Mutex var mu2 sync.Mutex var wg sync.WaitGroup wg.Add(2) go op1(&mu1, &mu2, &wg) go op2(&mu1, &mu2, &wg) wg.Wait() }

    作者回复: 👍

    2022-01-15
    5
    10
  • aoe
    读到这里,明白了 Go 的并发不是万能的! 1. Go 基于 Tony Hoare 的 CSP 并发模型理论,实现了 Goroutine、channel 等并发原语; 2. 使用低级同步原语(标准库的 sync 包以及 atomic 包提供了低级同步原语:Mutex/RWMutex/Cond 等)的性能可以高出 channel 许多倍 3. 有锁的地方就有江湖,高并发下的性能主要拼的是算法,没有一门语言有压倒性优势

    作者回复: 👍

    2022-01-18
    6
  • Geek_62c21a
    老师,有两个问题请教您 1,wg.Done() 是不是在函数开始的地方写成 defer wg.Done() 会好点呢? 2,条件变量 broadcast 的时候,以下两种写法有区别吗? 第一种: groupSignal.L.Lock() ready = true groupSignal.Broadcast() groupSignal.L.Unlock() 第二种: groupSignal.L.Lock() ready = true groupSignal.L.Unlock() groupSignal.Broadcast()

    作者回复: 1. 可以的。 2. 虽然Broadcast的参考文档也提到了:It is allowed but not required for the caller to hold c.L during the call. 对于例子来说两种写法都是ok的。但条件变量的测试条件表达式,比如本例子中的ready最好是始终在lock的保护下的。所以这里我建议用了第一种。否则在复杂的场景下,一旦unlock,条件变量测试表达式 就不受控了。

    2022-04-25
    5
  • Calvin
    老师,Go 中没有可重入的锁吗?

    作者回复: 没有。Go团队认为递归锁或可重入锁是一个bad idea,所以不支持。

    2022-01-15
    9
    5
  • 非梧桐不止
    老师,既然共享内存的性能比channel好,为什么Go倡导用通信去共享内存呢?

    作者回复: 多数情况下,对性能没有那么高要求,采用基于csp模型的并发设计是主流。

    2022-05-27
    2
    4
  • singularity of space time
    老师,请问一下Go在没有volatile的情况下如何保证共享变量在不同Goroutine的可见性?如果可见性不能保证的话,那么CAS的正确性应该也不能保证吧?还是说CAS内部的实现已经保证了可见性呢?

    作者回复: Go中没有volatile。但go给出了自己的memory model,https://go.dev/ref/mem 这个也许能回答你的问题。

    2022-02-06
    2
    3
  • 文经
    白老师,我觉得条件变量的例子完全可以用channel来实现,代码逻辑还会更简单一些。 使用条件变量,跟Mutex一样,是基于性能的考虑吗?

    作者回复: 你的想法没错。但条件变量的性能我还真没有和channel对比过,介绍条件变量只是为了给大家“科普”一下它的存在和适合的场景。说实话,条件变量使用的很少。

    2022-01-21
    3
  • 文经
    白老师: “atomic 原子操作可用来同步的范围有比较大限制,只能同步一个整型变量或自定义类型变量。如果我们要对一个复杂的临界区数据进行同步,那么首选的依旧是 sync 包中的原语。” 这里说的整型变量和自定义类型变量,是不是可以理解为一个字长的大小。在64位cpu上就是8个字节。因为CPU通过数据总线,一次从内存中最多只能获取一个字长的信息。所以atomic的限制也是一个字长。

    作者回复: 你说的没错,无论整型变量和自定义类型变量,atomic的操作实质上针对的都是字长长度的指针。但我说的复杂临界区,还有另外一个情况,那就是在锁内部有着对数据的更为复杂的判断与操作,而不仅仅是+n,-n,或cas。

    2022-01-21
    3
收起评论
显示
设置
留言
21
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部