知识卡片

并发正确性的核心判断标准:不是用了哪种同步策略,而是共享资源有没有被真正保护

普通读书笔记卡

内容

很多程序员的简历上写着”精通并发编程/熟悉多线程机制”,聊起锁、互斥、线程池、同步、信号量这些名词也头头是道,但真让他们现场写一段简单的并发程序时,能写好的却不多——并发编程的真实难度被严重低估:如果说写好一段普通同步代码的难度是5,并发编程的难度可以达到100,很多看似稳定的程序,一旦面对并发场景依然可能出问题。判断一段代码能不能高质量地处理并发,关键不在于它有没有应用某种具体的同步策略(用没用锁、用没用信号量),而在于代码里的共享资源是否真的得到了保护,具体可以拆解成四类典型的风险点:一是局部变量之外的内存访问都有并发风险(比如访问对象属性、访问静态变量);二是访问共享资源本身有并发风险(比如共享的缓存、数据库);三是被调用方如果没有明确声明是线程安全的,很可能存在并发问题(比如普通的HashMap);四是所有依赖时序的操作,即使组成这个操作的每一步单独看都是线程安全的,整体依然可能存在并发问题(比如先删除一条记录、再把计数减一这样的两步操作,即使删除和减一各自都是线程安全的原子操作,两步之间依然可能被其他线程插入进来破坏预期的顺序结果)。前三种风险通常能通过阅读代码本身比较简单地识别出来,只要培养起对共享资源访问的敏感度就可以;但第四种”依赖时序”的风险往往很难通过单纯看代码发现,甚至这类并发问题的两处相关调用可能根本不在同一个程序里(比如两个系统同时读写同一个数据库、或者并发调用了同一个程序的不同模块)——只要代码里出现了不加锁、访问共享资源的”先做A、再做B”这类逻辑,就应当提高警惕。这个案例给出的核心方法论是:评价并发代码的正确性,应该把注意力从”用了什么同步机制”转移到”共享资源的访问路径有没有被完整地识别和保护”上,前者是手段,后者才是真正决定正确性的本质问题。

参考来源

- 位置:《高可用架构(第1卷)》第5章《运维保障》"5.6 系统运维之评价代码优劣的方法"节,"5.6.1 什么是好代码"(源文件:_epub-src/OEBPS/Text/Chapter5_6_2.xhtml) - 结论依据:原文说明"能否高质量地实现并发编程的关键并不是是否应用了某种同步策略,而是看代码中是否保护了共享资源",并逐一列出四类风险点,特别说明"所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题……往往很难简单地通过看代码的方式看出来",直接支撑本卡片结论。 - 原始内容:能否高质量地实现并发编程的关键并不是是否应用了某种同步策略,而是看代码中是否保护了共享资源……所有依赖时序的操作,即使每一步操作都是线程安全的,还是存在并发问题(比如先删除一条记录,然后把记录数减一)……只要是代码里出现了不加锁的,访问共享资源的"先做A,再做B"之类的逻辑,可能就需要提高警惕了。