藏川线前段

--- 摄于 2017 年 9 月 藏川线前段

并发正确是永远的痛

大概一个多月之前,我就陷入了被并发问题包围的状态,几乎每一次代码改动,都会被并发问题 gank,而且有些是极其难以复现的问题,这篇算是对一些问题进行总结梳理叭。

案例一

在提交合并这个 PR 的时候,引入了一个很微小的问题,从代码上基本无法看出问题,可以很清晰地看出:在状态变为 Started 的时候,n_sync_started 会加一; 在状态变为 suspend_synctip_synced 的时候,对应 n_sync_started 会减一。正常是不可能因为这样的操作出现 overflow 问题的,因为加减都必然对应一个状态,但是,在并发修改的时候,这样的问题就有可能出现,进而导致 overflow 问题,于是就有了这个 PR ,用来修复并发修改下的不一致问题。

案例二

那么并发问题如果使用支持并发操作的各种数据结构是不是就没有问题了呢?

并不是,甚至会导致更加复杂并难以复现的并发问题。

在修复其他问题的时候,这个 PR 使用了正常的支持并发操作的 hashmap,逻辑并没有什么问题,并且在大部分情况下已经修复了 PR 中对应的 test 所涉及的问题,但在 7*24 小时两周后,还是出现了 3 例没有通过的测试,一次测试大概 10 分钟,换算成概率是 30/(24*60*7*2) = 0.00148,虽然概率不到 1% 但还是有问题,而且通过代码查看实现,几乎看不出问题,真正的问题在哪?

原因在于,这里的数据结构是需要整体一致性的,因为它有并发删除和并发插入,三个 map 在整体上需要保持对外的一致性,而使用三个各自支持并发的 map 并不能满足这个条件,于是在偶然的情况下,会造成数据不一致,进而造成测试失败,于是有了这个 PR 用来修复一致性问题。

案例三

在这个 PR 之前,ckb 内置的 miner 是会偶尔提交叔块的,虽然目前不太有可能有人依赖这个内置的 miner 去做事情,但这其实是一件很令人奇怪的事情,一个 miner 为什么有能力自己 gank 自己呢,实际上这也是并发问题,为了让开发者有相对好的体验,在 dev chain 的 miner 上,我们禁用掉了定时 poll 最新 block template 的功能,使得 dev chain 肯定不会出叔块,但是它其实会带来一些副作用,比如 ckb 重启之后,miner 必须跟着重启,而不是自动连上并工作。

PR 修复完上面的问题之后,将禁用的定时 poll 功能重新打开,并且保证 miner 不再会提交叔块,但是,没想到的是,之前的临时方案没有删除干净,功能重新启用带来了并发问题,导致 miner 可能因为 poll 的行为而卡死,比出叔块还更恶劣的行为,于是有了另一个 PR 用来修复卡死的问题。

最后

以目前计算机世界的架构来看,并发幽灵将持续伴随我们存在,并时不时 gank 一下以表达它的存在感,我们几乎无法避免这个问题的出现,有些问题也无比难以复现,就像 案例二 中,百分之一不到的机率出现,有时候甚至可以忽略,而如果忽略了这个问题,那势必在某些生产场景下被打脸。

评论区

加载更多

登录后评论