本书是共享内存并行编程领域兼具理论深度与工程价值的权威指南,专为操作系统内核、并行数据管理系统及低级库的底层开发者打造。本书颠覆了"并行编程高不可攀”的固有认知,指出其核心挑战在于设计陷阱与概念框架的缺失。作者将并行编程从"黑艺术”重构为系统化工程学科,通过拆解具体任务、提炼可复用的设计技巧,为开发者提供切实可行的实践方案。书中既深入剖析了现代硬件特性对并行软件性能的影响,又全面覆盖了锁、数据所有权、读-复制-更新(RCU)等同步技术,还详解了开发工具、可扩展性设计策略与验证方法。同时,本书独树一帜地强调人因因素,关注程序员在应对复杂共享内存系统时的理性决策逻辑,助力开发者高效攻克并行编程难题。
保罗?E?麦肯尼(Paul E. McKenney)是世界级并行编程专家,Linux 内核中 RCU 实现和 rcutorture 测试模块的维护者,也是RCU的发明人。对于实时操作系统内核同步机制(例如 Linux 中的实时 RCU)、Linux 和 UNIX 操作系统内核中的 SMP/NUMA 可扩展性和性能、网络性能分析、路由和拥塞控制, 嵌入式实时应用程序有着丰富的经验和研究。 Paul 曾担任IBM Linux 技术中心杰出工程师,发表过 200 多篇论文,拥有 100 多项专利。他的专长包括多线程和多核系统的性能编程、NUMA 架构、开源软件项目。
2009年毕业于成都电子科技大学。加入中兴通讯操作系统团队后,踏上了Linux内核的征程。曾任中兴通讯操作系统部开源总监,推动中兴通讯加入Linux基金会。后曾任职腾讯,曾在波士顿任职于VoltDB,从底层到应用层,着力打造世界最快的内存数据库。
第1章 如何使用本书1
1.1 路线图2
1.2 小问题3
1.3 本书之外的选择4
1.4 示例源代码5
1.5 这本书属于谁6
第2章 简介8
2.1 导致并行编程困难的历史原因9
2.2 并行编程的目标10
2.2.1 性能11
2.2.2 生产率12
2.2.3 通用性13
2.3 并行编程的替代方案15
2.3.1 运行多个串行应用实例16
2.3.2 利用现有的并行软件16
2.3.3 性能优化17
2.4 是什么使并行编程变得复杂17
2.4.1 任务分片18
2.4.2 并行访问控制19
2.4.3 资源分片和复制20
2.4.4 与硬件交互20
2.4.5 组合使用20
2.4.6 语言和环境如何支持这些任务21
2.5 本章的讨论21
第3章 硬件和它的特性22
3.1 概述22
3.1.1 流水线式的CPU23
3.1.2 内存引用25
3.1.3 原子操作26
3.1.4 内存屏障27
3.1.5 缓存未命中27
3.1.6 I/O操作28
3.2 开销29
3.2.1 硬件体系结构29
3.2.2 操作的开销30
3.2.3 硬件上的优化33
3.3 硬件免费午餐34
3.3.1 3D集成35
3.3.2 新材料和新工艺36
3.3.3 用光来替换电子36
3.3.4 专用加速器37
3.3.5 已有的并行软件37
3.4 对软件设计的启示38
第4章 编程的工具40
4.1 脚本语言40
4.2 POSIX多进程42
4.2.1 POSIX进程的创建和销毁42
4.2.2 POSIX线程的创建和销毁44
4.2.3 POSIX锁46
4.2.4 POSIX读写锁50
4.2.5 原子操作(经典GCC)53
4.2.6 原子操作(C11)55
4.2.7 原子操作(现代GCC)55
4.2.8 每线程变量55
4.3 POSIX接口的替代者56
4.3.1 代码组织和初始化56
4.3.2 线程创建、销毁和控制56
4.3.3 锁操作59
4.3.4 访问共享变量60
4.3.5 原子操作71
4.3.6 每CPU变量72
4.4 正确的工具:如何选择73
第5章 计数75
5.1 为什么不可小看并发计数76
5.2 统计计数器79
5.2.1 设计79
5.2.2 基于数组的实现79
5.2.3 基于每线程变量的实现81
5.2.4 最终一致性的实现84
5.2.5 本节讨论86
5.3 近似上限计数器86
5.3.1 设计87
5.3.2 简单上限计数器的实现88
5.3.3 简单上限计数器的讨论95
5.3.4 近似上限计数器的实现95
5.3.5 关于近似上限计数器的讨论96
5.4 精确上限计数器96
5.4.1 原子上限计数器的实现96
5.4.2 关于原子上限计数器的讨论103
5.4.3 信号-窃取(Signal-Theft)上限计数器的设计103
5.4.4 信号-窃取上限计数器的实现104
5.4.5 关于信号-窃取上限计数器的讨论111
5.4.6 精确上限计数器的应用111
5.5 关于并行计数的讨论112
5.5.1 并行计数的性能113
5.5.2 并行计数的专门化114
5.5.3 从并行计数中学到了什么115
第6章 对分片和同步的设计117
6.1 分片练习117
6.1.1 哲学家就餐问题118
6.1.2 双端队列120
6.1.3 关于分片问题示例的讨论128
6.2 设计准则128
6.3 同步粒度131
6.3.1 串行程序131
6.3.2 代码锁133
6.3.3 数据锁134
6.3.4 数据所有权136
6.3.5 锁粒度和性能137
6.4 并行快速路径139
6.4.1 读写锁140
6.4.2 层次锁141
6.4.3 资源分配器缓存142
6.5 分片以外148
6.5.1 使用工作队列的迷宫问题的并行解法149
6.5.2 迷宫问题的另一种并行解法151
6.5.3 性能比较I154
6.5.4 另一种迷宫问题的串行解法156
6.5.5 性能比较II156
6.5.6 未来展望与本节总结158
6.6 分片、并行化与优化158
第7章 锁159
7.1 努力活着160
7.1.1 死锁161
7.1.2 活锁与饥饿169
7.1.3 不公平的锁171
7.1.4 低效率的锁172
7.2 锁的类型172
7.2.1 互斥锁172
7.2.2 读写锁173
7.2.3 读写锁之外的锁174
7.2.4 作用域锁175
7.3 锁在实现中的问题178
7.3.1 基于原子交换的互斥锁实现示例178
7.3.2 互斥锁的其他实现179
7.4 基于锁的存在性保证181
7.5 锁:是英雄还是坏蛋184
7.5.1 应用程序中的锁:英雄184
7.5.2 并行库中的锁:只是一个工具184
7.5.3 并行化串行库时的锁:坏蛋188
7.6 本章总结190
第8章 数据所有权191
8.1 多进程192
8.2 部分数据所有权和pthreads192
8.3 函数输送193
8.4 指派线程194
8.5 私有化194
8.6 数据所有权的其他用途195
第9章 延迟处理196
9.1 运行示例196
9.2 引用计数198
9.3 危险指针203
9.4 顺序锁210
9.5 读-复制-更新216
9.5.1 RCU介绍217
9.5.2 RCU基础机制225
9.5.3 Linux内核RCU API234
9.5.4 RCU的使用246
9.5.5 RCU相关工作264
9.5.6 RCU练习268
9.6 如何选择268
9.6.1 概述268
9.6.2 细节270
9.6.3 产品应用272
9.7 如何处理写操作274
第10章 数据结构275
10.1 示例应用275
10.2 可分片数据结构276
10.2.1 哈希表设计276
10.2.2 哈希表的实现277
10.2.3 哈希表的性能280
10.3 主读数据结构283
10.3.1 受RCU保护的哈希表的实现283
10.3.2 受RCU保护的哈希表的性能分析285
10.3.3 受RCU保护的哈希表的相关讨论290
10.4 不可分片的数据结构292
10.4.1 可变长哈希表的设计292
10.4.2 可变长哈希表的实现294
10.4.3 对可变长哈希表的探讨302
10.4.4 其他类型的可变长哈希表303
10.5 其他数据结构306
10.6 微优化307
10.6.1 专用化307
10.6.2 位与字节308
10.6.3 硬件适配309
10.7 本章总结311
第11章 验证312
11.1 简介313
11.1.1 Bug来自何处313
11.1.2 验证时所需的心态314
11.1.3 应该从何时开始验证316
11.1.4 开源之路317
11.2 跟踪319
11.3 断言320
11.4 静态分析321
11.5 代码评审321
11.5.1 审查322
11.5.2 走查322
11.5.3 自查323
11.6 概率及海森堡Bug325
11.6.1 离散测试统计326
11.6.2 简单离散测试统计328
11.6.3 持续测试统计328
11.6.4 定位海森堡Bug330
11.7 性能评估334
11.7.1 性能基准测试335
11.7.2 剖析335
11.7.3 差异分析336
11.7.4 微基准测试336
11.7.5 隔离337
11.7.6 检测干扰338
11.8 本章总结342
第12章 形式化验证345
12.1 通用目的的状态空间搜索345
12.1.1 Promela和Spin346
12.1.2 如何使用Promela352
12.1.3 Promela示例:锁355
12.1.4 Promela示例:QRCU358
12.1.5 Promela初试牛刀:可抢占RCU和dynticks368
12.1.6 验证dynticks和可抢占RCU374
12.2 特定目的的状态空间搜索402
12.2.1 对litmus测试的剖析403
12.2.2 litmus测试代表什么404
12.2.3 运行litmus测试405
12.2.4 PPCMEM讨论407
12.3 公理方法408
12.3.1 公理方法和加锁410
12.3.2 公理方法和RCU412
12.4 SAT求解器415
12.5 无状态模型检查器417
12.6 本章总结418
12.7 选择验证方案419
第13章 合而为一422
13.1 计数器难题422
13.1.1 计数更新422
13.1.2 计数查找423
13.2 引用计数再改造423
13.2.1 引用计数分类的实现425
13.2.2 计数器优化431
13.3 使用危险指针做辅助431
13.3.1 可扩展的引用计数431
13.4 顺序锁的特殊用法432
13.4.1 关联数据元素432
13.4.2 升级为写端433
13.5 使用RCU补救程序434
13.5.1 RCU和基于每线程变量的统计计数器434
13.5.2 针对可移除I/O的RCU和计数器437
13.5.3 数组和长度438
13.5.4 关联字段440
13.5.5 对更新友好的遍历441
13.5.6 再谈可扩展引用计数441
第14章 高级同步442
14.1 避免使用锁442
14.2 非阻塞同步443
14.2.1 简单NBS444
14.2.2 对NBS优点的应用447
14.2.3 NBS相关讨论449
14.3 并行实时计算450
14.3.1 什么是实时计算451
14.3.2 谁需要实时计算456
14.3.3 谁需要并行实时计算457
14.3.4 并行实时系统的实现458
14.3.5 并行实时操作系统的实现459
14.3.6 实现并行实时应用472
14.3.7 实时计算和高速计算:如何选择477
第15章 高级同步:内存序479
15.1 有序:为什么和怎么做479
15.1.1 硬件为什么会打乱内存访问顺序481
15.1.2 如何强制有序483
15.1.3 基本经验法则486
15.2 技巧和陷阱488
15.2.1 具有多个值的变量488
15.2.2 内存引用重排491
15.2.3 地址依赖495
15.2.4 数据依赖497
15.2.5 控制依赖498
15.2.6 缓存一致性500
15.2.7 多拷贝原子性501
15.3 编译时的困惑515
15.3.1 内存引用的限制516
15.3.2 地址依赖和数据依赖的困难517
15.3.3 控制依赖造成的灾难521
15.4 高级原语527
15.4.1 内存分配527
15.4.2 RCU528
15.5 硬件相关的内存序540
15.5.1 Alpha543
15.5.2 ARMv7-A/R546
15.5.3 ARMv8547
15.5.4 Itanium548
15.5.5 MIPS549
15.5.6 POWER/Powe