Go并发编程一年回顾

栏目: IT技术 · 发布时间: 3年前

内容简介:距离我2019年的如果让你实现一个

距离我2019年的 深入 Go 并发编程研讨课 发布也有一年时间了。在Gopher Beijing 2019相关演讲后我整理了这一个8小时的课程,对有志于深入理解Go并发编程原理的同学从深度和广度上提供一些帮助。当然一年来我也一直关注着Go并发编程的演变,并且补充了池和并发模式的一些例子。对于官方的并发库来说,这一年来又有哪些变化呢,让我们快速回顾一下。

Sync.Once 很简单?

如果让你实现一个 Once 一样的库,是不是觉得很简单?因为Go标准库提供了 atomic 的CAS操作,所以我们一般都会使用一个 uint32 的flag标志,用来标记是否执行过一次,这是我们在Go编程中经常使用的一种模式,但是这里有一个陷阱,很容易出现资源还没有初始化完成就被使用的情况。

Go并发编程一年回顾

这个注释说的已经很明确了,下面的只通过一个flag来保证执行 f 一次是不够的。因为如果同时有两个goroutine调用这一行代码,一个goroutine成功CAS设置了标志的话,就会调用 f ,做资源初始化或者其它的一些事情,这个执行可能会耗费一段时间。同时另外一个goroutine设置不成功,它想当然的认为另外一个goroutine已经执行了 f ,但是实际上 f 可能还没有执行完,这就可能代码并发的问题。

因为不止一次有人询问,所以 Russ Cox 干脆在方法前面加了一些注释,希望能帮助都一些探究原理的人们。

if atomic.CompareAndSwapUint32(&o.done,0,1) {
	f()
   }

那么Go标准库是如何解决这个问题的呢?标准库的处理很很简单,那就是双检查。如果在调用 f 有并发问题,那么通过一个Mutex保证只有一个goroutine执行,这样能保证并发的goroutine会进入阻塞状态,然后再double-checking看看有没有并发的其它goroutine已经执行了 f

Go并发编程一年回顾

优化Mutex

这次对Mutex的优化并没有涉及到Mutex的代码,而是对Mutex的实现中依赖的内部信号量( sema )实现的一个优化。

在Mutex实现中,一旦一个Goroutine释放了这个排外锁,那么它会将这个锁交给等待队列中第一个waiter(饥饿模式下)或者唤醒一个waiter竞争。waiter的休眠和唤醒是通过信号量来实现的。既然我们唤醒了一个waiter, 而且没有其它goroutine来和它争抢,那么我们期望这个goroutine能尽快的执行,所以代码做了一些优化。

有一个issue专门讨论这个优化: #33747

Go并发编程一年回顾

sync.Map增加了LoadAndDelete方法

先前 sync.Map 要实现如果元素存在就删除的原子操作是不可能的,下面的代码不是原子操作,所以可能“误伤”:

actual, ok := m.Load("key")
if ok {
  m.Delete("key")
  doSomething(actual)
}

说起 sync.Map 这个数据结构其实是挺尴尬的,它是为了解决编程中使用map经常遇到的并发问题后来才增加的,内部采用两个map进行数据的“腾挪”,可以对读多写少的场景进行优化。因为适用于特定的场景,而且还需要对interface和实际类型之间的转换,所以并没有广泛的应用。明年Go泛型发布之后,不需要手工的在接口和实际类型之间做转换了后,会方便不少。

#33762 对这个问题有详细讨论,很多讨论陷入了方法名应该是什么的问题。从项目管理的角度看,team leader应该刹住无谓的讨论而下个定论,Russ Cox出马了,拍板定为 LoadAndDelete

虽然issue被接收了,但是几乎一个月了也没有人动手,最后还是 欧长坤 提交实现了。

原先内部的 delete 方法不管元素存在不存在直接就把一个元素删就删了,现在既然我们需要返回要删除的对象,所以这个方法需要改动一下,如果元素存在,就把元素返回。

LoadAndDelete 在调用 delete 删除的时候,就可以把要删除的元素返回了。 Go 1.15发版的时候应该就包含这个方法了。

static lock ranking

Go的运行时和标准库的代码经过十多年的锤炼,应该说相当的稳定了,但是即使是这样,

Austin Clements实现了记录锁的日志的 支持 ,它可以用来构造和分析运行时锁的顺序图,它可以把锁信息发给文件进行分析:

Go并发编程一年回顾

Dan Scales利用这个技术建立了Static lock ranking的技术。通过文档化记录锁的总顺序,再和实际期望的锁顺序进行比较,如果有死锁现象出现,那么就会被检查到。通过这个技术,发现了timer中的一个 死锁#35532 )。

正确释放锁的姿势

永远记住,总是要释放锁,无论出现什么情况,这不,北邮的Boqin Qin就提交了一个在panic的时候也要释放锁的修复。

虽然,很多情况下panic的时候我们程序就退出了,释不释放锁貌似没有太大的问题。但是如果你把代码提供一个库给其他人使用的话,别人可能使用recover去处理这个panic,这种情况下会导致锁没有释放。

所以记住,锁总是要释放,无论出现什么情况。

Go并发编程一年回顾


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

编写可读代码的艺术

编写可读代码的艺术

Boswell, D.、Foucher, T. / 尹哲、郑秀雯 / 机械工业出版社 / 2012-7-10 / 59.00元

细节决定成败,思路清晰、言简意赅的代码让程序员一目了然;而格式凌乱、拖沓冗长的代码让程序员一头雾水。除了可以正确运行以外,优秀的代码必须具备良好的可读性,编写的代码要使其他人能在最短的时间内理解才行。本书旨在强调代码对人的友好性和可读性。 本书关注编码的细节,总结了很多提高代码可读性的小技巧,看似都微不足道,但是对于整个软件系统的开发而言,它们与宏观的架构决策、设计思想、指导原则同样重要。编......一起来看看 《编写可读代码的艺术》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具