教科书评论:并行编程是否困难,如果困难,你能做什么?
摘要
作者评论了由Paul E. McKenney编写的免费在线教科书《Is Parallel Programming Hard, And, If So, What Can You Do About It?》,讨论了其对无锁并发和CPU缓存效应的关注,并分享了在度假期间阅读此书的个人背景。
<p><a href="https://lobste.rs/s/73ei6g/textbook_review_is_parallel_programming">评论</a></p>
查看缓存全文
缓存时间: 2026/09/21 16:26
# 教科书评:并行编程有多难?如果很难,你能怎么办?
来源:https://ahelwer.ca/post/2026-09-21-concurrency-textbook/
以下是我对Paul E. McKenney(Linux内核RCU同步机制(https://docs.kernel.org/RCU/whatisRCU.html)的作者)所著的免费在线教科书《并行编程有多难?如果很难,你能怎么办?》(https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html)的一些想法。我阅读了这本教科书的大部分内容,收获颇丰,并且近期可能不会再继续深入,因此想趁着记忆犹新写下这篇评论。
## 情境与背景
这里我将谈谈开始阅读这本教科书时的个人心态、人生阶段和物理环境。这可能就像食谱网站上冗长乏味的个人风味前言,如果你对此不感兴趣,可以直接跳过。
过去十年,我的职业生涯一直围绕TLA+和分布式系统展开,我觉得是时候做出改变了。那是一个过渡且情绪动荡的时期!2022年,我曾尝试(并失败了)转向Lean,希望能获得一个极其冷门且不存在的工作——为研究人员的形式化量子信息处理结果。我因此精疲力竭,考虑到自动化定理证明的最新进展,我那暂时具有预见性的神经系统可能刚刚让我躲过了一劫。正是在这样的历史背景下,我参加了2026年在密苏里州哥伦比亚市举办的“软件理应正常运行”(https://softwareshould.work/)会议。
会议有很多精彩的演讲,但我特别喜欢Filip Pizlo关于Fil-C的那场:
我也与Fil交谈了许多,从解释器谈到并发。我曾自以为凭借TLA+和分布式系统的经验对并发了解颇深,但Fil向我讲述了编写并发无锁垃圾回收器的困难,我意识到自己其实对并发知之甚少(在一次好的会议中,意识到自己知之甚少正是其价值所在)。Fil还提到,TLA+在推理那些**字面意义上**并发发生的事件(这在多核CPU中是可能的!)时可能不太有用(或至少不够符合人体工学),并且分析并发算法的线性一致性非常重要,而我对此概念的理解仅限于分布式系统的层面。
这一切都显得非常诱人,于是我开始寻找一本更侧重于无锁方面,而非基于互斥锁或消息传递模式的并发教科书。《并行编程有多难?如果很难,你能怎么办?》(https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html)似乎符合要求,因为它关注的是通用的并发编程和CPU缓存效应,而非更具体的关于如何编写无锁数据结构的教科书。它还在Hacker News上引发过几次(2023年(https://news.ycombinator.com/item?id=36318280),2021年(https://news.ycombinator.com/item?id=26537298),2020年(https://news.ycombinator.com/item?id=22030928),2015年(https://news.ycombinator.com/item?id=9315152),2014年(https://news.ycombinator.com/item?id=7381877),2011年(https://news.ycombinator.com/item?id=2784515))适度有趣的讨论。我认为花时间在分析瘫痪中试图找到“正确”的教科书是没用的(这其实只是拖延的巧妙方式),所以它看起来足够好了。
阅读这本教科书的物理环境是我在加拿大一个安静、林木繁茂的地方探亲的一周半假期。2026年也恰好是蚊子异常肆虐的季节。因此,大部分时间我都坐在一个凉爽的纱窗门廊里,尽职地由数百名“守卫”看守,确保我不会离开岗位:
即使在更偏远的地区,卫星和蜂窝网络也让令人分心的网络连接变得异常便捷,但除此之外,这里堪称理想的教科书阅读地点。
## 教科书格式
关于教科书实际结构的一些快速说明;它至少提供**三种**独立的格式,均为PDF:
1. 双栏格式,类似科学论文
2. 单栏格式,带有宽边距
3. 单栏格式,无边距
最后一种格式非常适合在我的Pine64 PineNote(https://pine64.org/devices/pinenote/)上阅读。
教科书包含大量内部链接。其中一些链接用于快速知识检查框,点击链接会跳转到问题的答案。其他链接则在提及图表或章节时使用,或用于大量的脚注和引用。不幸的是,后者非常烦人,应该至少减少80%。如果你的电子阅读器没有物理翻页按钮,那么你的阅读体验将包括在你本意想翻页时,不断意外按下这些链接,然后被发送到天知道哪里。电子书导航本身就已经够令人困惑了,没有这个功能会好很多。而且这几乎意味着不可能通过反复点击翻页在两个章节之间快速来回跳转。在我**确实**想点击链接的时候,有些地方有两个链接紧挨在一起;触摸屏缺乏足够的精确度来可靠地点击其中一个而非另一个。禁用所有链接的解决方案确实存在,但这样你就无法访问那些相当不错的知识检查问题了。所以我只好忍受着读完了。
## 教科书内容 - 导论章节
这本教科书的内容呈现方式几乎完美契合我的水平。本书以轻松愉快的介绍和动机章节开篇,然后在第三章“**硬件及其习性**”中迅速切入正题。在这里,我们从宏观层面了解现代CPU的工作原理——是什么让它们快,又是什么让它们慢。其中还包含许多幽默的插图!第3.2.1节“**硬件系统架构**”才是让我真正感到有趣的地方,我们通过一个简化的例子了解CPU核心如何向其缓存中不存在的内存地址写入数据。
这里有一个遗憾:如果我当时对MESI协议(https://en.wikipedia.org/wiki/MESI_protocol)有基本的了解,我本会受益匪浅,因为我对CPU缓存如何协调并发读写操作基本没有直觉。我是在搜索网上资料以更好地理解这一节时才发现MESI的;MESI仅在本书的附录中被提及。但了解它之后,我对本书其余部分的理解大大加深了。
了解MESI也让我明白,多个CPU核心无法在字面意义上同时写入同一数据位置!x86 CPU实际上并不直接写入内存,它只写入其缓存(缓存值最终会被刷回内存)。x86 CPU核心只有在拥有包含该地址的缓存行的独占所有权时,才能写入该特定地址。如果另一个核心试图同时写入该地址,它必须等待独占所有权转移到它。因此,字面意义上的并发写入实际上不会发生。不过,如果写入的数据跨越多个缓存行,则可能发生写入撕裂。
第四章“**常用工具**”的内容绝对令人耳目一新。在这里我们了解到,如果你在编写并行程序时不加小心,编译器会尝试令人难以置信的创造性优化,导致完全荒谬的行为!第4.3.4.1节“**共享变量的小把戏**”涵盖了诸如加载撕裂、存储撕裂、加载融合、存储融合、代码重排、凭空加载、凭空存储(尤其恶劣)、存储到加载转换以及死代码消除等恐怖意外。*然后*,假设你的代码在编译过程中幸存下来,该章节继续探讨CPU在实际执行你的程序时可能搞出的荒唐事!这双重麻烦让我很难清晰地思考超越熟悉的互斥锁或消息传递之外的并行程序。
我对这一章的一个问题是它非常特定于Linux内核环境。我本希望能了解到C++11和C11为形式化并行编程所做的工作,例如`std::memory_order`(https://en.cppreference.com/cpp/atomic/memory_order)。这些内容仅在第4.2.6和4.2.7节“原子操作(C11)”和“原子操作(现代GCC)”中用简短的段落提及。我离开这一章时,只模糊地知道`ACCESS_ONCE()`和`WRITE_ONCE()`宏只是将内容转换为`volatile*`,这就足以吓退编译器了。一些有趣的历史发展,比如关于良性数据竞争是否是错误的辩论(https://www.usenix.org/legacy/event/hotpar11/tech/final_files/Boehm.pdf),则被完全跳过了。
## 教科书内容 - 主要章节
第五章“**计数**”可能是本书的招牌章节。它也是我深入阅读的最后一章。该章节涵盖了大约10种不同的方式来编写一个多个线程递增计数器的程序。那个显而易见且没有问题的实现(每个线程使用原子递增指令)在早期就被展示出来其性能有多差,从而被否定了。这时我课外对MESI的理解真的派上了大用场。
该章节最终介绍了一种叫做“信号窃取极限计数器”的东西,老实说,我并没有完全理解它。我想如果我自己写计数器,可能不会走那么远。我真的很喜欢基于数组的每线程统计计数器,它们与分布式系统中无冲突复制数据类型相似。它们也是学习伪共享性能影响的绝佳载体——你不能只是把所有线程特定的计数器都扔进一个连续的数组就完事了!
第五章之后,我基本只是浏览材料,寻找感兴趣的主题。有些章节相当概念化,讨论了所有权、分区、延迟处理以及可以从分布式系统中轻松转化过来的其他内容。“**形式验证**”章节使用了Promela和Spin,作为一个TLA+用户,我并不想学习这些。“**验证**”章节有一个很好的部分,11.6.4“**寻找海森堡缺陷**”。无锁编程直到第14章“**高级同步**”才真正被涵盖,而到那时,我已经准备好转而阅读专门关注无锁编程和数据结构的教科书了。第15章最终涵盖了内存排序,但那时我已经转而利用课外资料来试图解决我的困惑了。
## 总体评价
虽然我只深入阅读了前五章,但我认为这本教科书非常出色。我这么说是因为它真的让我渴望了解更多关于并行编程的知识!工作日的午餐时间,我常常要努力克制自己不要把学到的任何新奇东西倾泻给同事。比如你知道形式化release-consume排序(https://en.cppreference.com/cpp/atomic/memory_order#Release-Consume_ordering)的失败吗?或者在x86上使用`mov`的基本对齐加载和存储是原子的?又或者凭空出现的值(https://www.cl.cam.ac.uk/~pes20/cpp/notes42.html)?或者DEC Alpha令人难以置信的弱内存模型?或者release-acquire语义(https://lwn.net/Articles/844224/)是如何工作的?或者确定性模拟测试**尚不能**(https://news.ycombinator.com/item?id=39771394)有效地测试无锁算法?又或者在ARM(v8之前)上的原子操作可能被抢占?这里面充满了喜剧般反直觉的荒唐事。我接下来想学习高性能垃圾回收。欢迎推荐!
## 讨论
- lobste.rs (https://lobste.rs/s/73ei6g/textbook_review_is_parallel_programming)
- Mastodon (https://discuss.systems/@ahelwer/117309516818857647)
- Hacker News (https://news.ycombinator.com/item?id=49787759)
- LinkedIn (https://www.linkedin.com/posts/ahelwer_needed-a-change-so-i-spent-vacation-reading-share-7507809335033561088-ERpu/)
相似文章
并行编程的禅意:内核的姿态
一篇博客文章,通过HipKittens论文的视角探索并行编程概念,重点介绍了AMD GPU上重叠计算与内存移动的八波乒乓调度,并与禅宗原则进行了哲学类比。
@reprompting: https://x.com/reprompting/status/2074133435401064486
一条详细总结《大规模并行处理器编程》一书的推文串,重点介绍CUDA和GPU编程概念、优化技术以及并行模式。
并行编程之禅
一篇反思性文章,将通信、同步等并行编程概念与禅宗哲学和人类内部协调联系起来。
免费午餐已终结:软件领域向并发性的根本性转变 (2005)
文章讨论了由于物理限制,CPU速度提升带来的免费性能增益的终结,并强调随着多核处理器的兴起,软件开发向并发性的根本性转变。
@vivekgalatage: 并行算法入门 https://cs.cmu.edu/~guyb/paralg/paralg/parallel.pdf…
来自卡内基梅隆大学的并行算法入门资源,涵盖基本概念和技术。