34|并发:如何使用共享变量?
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
Go语言并发编程中的共享变量使用及同步原语选择是本文的重点。文章首先介绍了Go语言的并发设计基于CSP并发模型,通过Goroutine和channel进行通信来共享内存。同时,也详细分析了sync包提供的低级同步原语,如互斥锁和读写锁,并通过性能基准测试展示了其在高性能临界区同步机制场景下的优势。此外,文章还介绍了条件变量sync.Cond的概念及在实际场景中的应用,强调了其在“等待某个条件成立”的场景下的作用。通过性能对比和注意事项的介绍,为读者提供了实用的技术指导和建议。总的来说,本文为读者提供了关于Go并发编程中共享变量使用的全面指南,帮助读者快速了解并掌握相关技术特点。文章还通过性能测试结果和对比,指出了atomic包更适合一些对性能十分敏感、并发量较大且读多写少的场合。最后,文章提出了思考题,引发读者对死锁问题的思考。文章内容丰富,涵盖了Go语言并发编程中的关键技术点,对读者具有很高的参考价值。
《Tony Bai · Go 语言第一课》,新⼈⾸单¥59
全部留言(21)
- 最新
- 精选
- 步比天下老师,我想问个低级的问题,什么是临界区啊,不太懂
作者回复: 临界区其实就是一个代码片段。但这个代码片段中有共享的数据,这些数据不支持多个goroutine的并发访问,只能通过像channel、锁等机制同步各个goroutine的访问。同一时间,只能有一个goroutine访问这段代码,修改或读取这段代码所共享的数据。
2022-01-16515 - 罗杰连续三节课都是需要花费 30 分钟才能阅读完,即使是复习也要超过 20 分钟,这几节的内容真的好充实。
作者回复: 👍
2022-01-14611 - 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-15510 - aoe读到这里,明白了 Go 的并发不是万能的! 1. Go 基于 Tony Hoare 的 CSP 并发模型理论,实现了 Goroutine、channel 等并发原语; 2. 使用低级同步原语(标准库的 sync 包以及 atomic 包提供了低级同步原语:Mutex/RWMutex/Cond 等)的性能可以高出 channel 许多倍 3. 有锁的地方就有江湖,高并发下的性能主要拼的是算法,没有一门语言有压倒性优势
作者回复: 👍
2022-01-186 - 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-255 - Calvin老师,Go 中没有可重入的锁吗?
作者回复: 没有。Go团队认为递归锁或可重入锁是一个bad idea,所以不支持。
2022-01-1595 - 非梧桐不止老师,既然共享内存的性能比channel好,为什么Go倡导用通信去共享内存呢?
作者回复: 多数情况下,对性能没有那么高要求,采用基于csp模型的并发设计是主流。
2022-05-2724 - singularity of space time老师,请问一下Go在没有volatile的情况下如何保证共享变量在不同Goroutine的可见性?如果可见性不能保证的话,那么CAS的正确性应该也不能保证吧?还是说CAS内部的实现已经保证了可见性呢?
作者回复: Go中没有volatile。但go给出了自己的memory model,https://go.dev/ref/mem 这个也许能回答你的问题。
2022-02-0623 - 文经白老师,我觉得条件变量的例子完全可以用channel来实现,代码逻辑还会更简单一些。 使用条件变量,跟Mutex一样,是基于性能的考虑吗?
作者回复: 你的想法没错。但条件变量的性能我还真没有和channel对比过,介绍条件变量只是为了给大家“科普”一下它的存在和适合的场景。说实话,条件变量使用的很少。
2022-01-213 - 文经白老师: “atomic 原子操作可用来同步的范围有比较大限制,只能同步一个整型变量或自定义类型变量。如果我们要对一个复杂的临界区数据进行同步,那么首选的依旧是 sync 包中的原语。” 这里说的整型变量和自定义类型变量,是不是可以理解为一个字长的大小。在64位cpu上就是8个字节。因为CPU通过数据总线,一次从内存中最多只能获取一个字长的信息。所以atomic的限制也是一个字长。
作者回复: 你说的没错,无论整型变量和自定义类型变量,atomic的操作实质上针对的都是字长长度的指针。但我说的复杂临界区,还有另外一个情况,那就是在锁内部有着对数据的更为复杂的判断与操作,而不仅仅是+n,-n,或cas。
2022-01-213