跳转至

并发与同步:从互斥锁到 zlocate

这一章讲什么: 多线程共享内存时怎么不把数据写坏。书的阶梯是这样的: 互斥锁(最简单) → 读写锁(读多写少) → 无锁设计(每人一个槽) → 原子操作(单值的精密同步) → 两组对照实验(丢更新长什么样、修好后长什么样) → 生产者-消费者(原子版与条件变量版) → 环形缓冲 → 收尾项目 zlocate,把一切用在一个真实的并行文件索引器上。

主线例子:两个。① 丢更新实验:25 个线程各加 10 万次,理论值 2,500,000,实际 2,499,996; ② zlocate 实测:14 个线程 0.26 秒爬完 13324 个目录、25908 个条目。

1. 先纠一个误解:并发 ≠ 并行

并发是把程序拆成能独立执行的部件的结构能力;并行是多个部件同时执行。一堆线程堵在一个共享资源上干等,不叫并行;反过来,设计良好的并发结构,在 OS 和硬件允许时才能安全地并行1。Erlang 在多核 CPU 出现之前很多年就这么干了。责任在设计的早期:别把问题拆成争一个变量的碎片,要拆成组合起来能解题的独立部件2。

这一章还有一个贯穿始终的取舍语法:用哪个同步原语,取决于问题的形状——互斥锁能护住任意复杂的不变量,但让所有访问者排队;原子操作无锁、只护一个值;无锁设计连同步都不要,但要求「每个线程拥有独立的槽、依赖关系事先已知」3。

2. 核心原理

2.1 互斥锁与读写锁:全有或全无,与读多写少的出路

互斥锁(mutex,mutual exclusion 的缩写)保证任一时刻只有一个线程能进临界区:一个线程拿到锁,其他试图拿锁的线程被阻塞,直到它释放。两个线程各加 1000 次,锁内加锁外放,结果可靠是 2000;没有锁,counter += 1 的读-改-写三步会被打断,结局随机4。

读写锁(RwLock)解决「读远多于写」的场景:普通互斥锁会强迫所有读线程互相等待,尽管读和读不冲突。RwLock 区分两种锁——lockShared() 读锁,可以多个线程同时持有;lock() 写锁,独占,持写锁时读写都禁止5。实测输出把差异演活了:写线程还在跑时,读者只能抢到 610、820 这种稀疏快照;写线程一完工,两个读者立刻自由交错、连续打印 1000——普通互斥锁会把每次读都串行化,RwLock 消灭了这份不必要的排队,这就是它存在的理由6。书也实话说:负载不忙时看不出差别7。

2.2 无锁设计:每个线程只写自己的槽

无锁不是不要同步,是把粗粒度锁换成精准的内存序保证。例子是一个多线程斐波那契:每个线程只负责算 fibs[i] 一个槽,任何线程不碰别人的槽——写-写竞争天然不存在;读前驱槽之前,先在一个自旋循环里等到前驱不再是哨兵值 unset(u64 最大值,表示「还没算出来」),每次自旋睡 1 微秒避免烧满一个核8。

为什么安全?靠的是 release/acquire 配对:写线程用 .release 存结果——它保证这次原子写之前的所有内存操作都对后来者可见;读线程用 .acquire 读——它保证这次读之后的操作不会被重排到读之前。两者建立 happens-before(先于)关系,读者看到的是完整写好的值,不是 CPU 存储缓冲区或缓存里的陈旧副本。在 ARM 这类弱内存模型上,省略这个配对即使代码逻辑正确也会出错误结果——处理器真的会重排9。

2.3 原子操作与内存序:一段代码的两个错误版本

std.atomic.Value(T) 是「对单个值不可分访问」的包装:读、写、改都是一条硬件指令,任何线程不能在中间插队——银行取钱就是这个性质的日常例子10。但光把变量声明成原子还不够,内存序(memory ordering)决定编译器和 CPU 被允许怎么重排围绕原子访问的指令。

第一个错误版本把「读+1+写」拆成 load(acquire) 和 store(release) 两个独立步骤——两步之间有个窗口,另一个线程可以插进来改值,造成丢更新。正确做法是 fetchAdd():整个加一是一条不可分割的读-改-写11。

第二个错误版本更微妙:锁保护了日志文件,却没保护计数器。代码先用普通变量读-改-写计数,再用互斥锁写日志——文件没写串行,但写进文件的数据本身已经坏了12。主线实验,25 个线程各加 10 万次:

无原子版:  Final counter value (expected 2500000): 2499996     ← 丢了 4 次
           但日志有 2,500,000 行!sort | uniq -d 揪出 4 个重复值
           (两个线程读到同一个值、各自加一、各自写日志,互相覆盖)

修正版:    @atomicRmw(usize, counter, .Add, 1, .seq_cst)
           Final counter value (expected 2500000): 2500000     ← 分毫不差,uniq -d 空空如也

图说:修正版用了 @atomicRmw(原子读-改-写,一条锁定的 CPU 指令)配 .seq_cst(顺序一致——最强的内存序,所有线程以单一全局顺序观察到原子操作,心智模型最简单;代价是严格序有性能成本,正确性和清晰度优先时选它)13。还有一个时序陷阱书特意点出:格式化字符串挪到了锁外(好,CPU 密集的活不该占锁),但代价是日志行可能乱序——想要严格递增,得把锁横跨「加一+写」两步14。

2.4 生产者-消费者:原子自旋版与条件变量版

同一个问题两套答案。问题形状:生产者造数据放进共享缓冲,消费者取出来处理,两边速率独立,共享缓冲需要同步15。

原子版(单槽):三个原子变量——buffer(值)、full(槽里有货吗)、done(生产者收工了吗)。生产者见槽满就睡 10 微秒重试,空了就 release 写值、置 full;消费者见 full 就读值、清 full,done 且空就退出。实测 5 个值,Produced 1 / Consumed 1 严格交替。代价书自己写在注释里:双线程都在忙等(自旋+睡眠轮询),比条件变量版费 CPU——原子给了细粒度控制,用错了地方(单槽忙等)就是用简单性换更高的 CPU 占用16。

条件变量版(环形队列):std.Io.Condition 与互斥锁配合,线程可以睡死等信号,而不是轮询。生产者在 count == max 时 cond.wait(io, &mutex)——原子地释放锁并挂起;每放一个就 cond.signal() 叫醒一个等货的消费者。消费者镜像对称:空就等,有货就取、signal 回去报「有空位了」。环形用 tail = (tail + 1) % max_buffer_size 取模回绕,天然的 FIFO17。实测:生产者发 5 个、消费者只收 3 个,多产的 3 和 5 留在缓冲里——「各方干各自被吩咐的活,缓冲守好每一次访问」18。

书给的取舍:原子版性能高但复杂易错(回绕、同步正确性),条件变量版协调逻辑简单、好读好维护、空闲时 CPU 零消耗——要极限速度和掌控选原子,要清晰可维护选条件变量;高级系统里两者混用19。

2.5 环形缓冲:为什么它是系统编程的常客

环形缓冲(循环缓冲)是定长数组 + 两个指针(读指针、写指针),取模回绕,插入取出都是 O(1),满了新数据盖最旧的。它解决的是系统编程里反复出现的一个问题:两个速度不同的部件要交换数据,谁也别阻塞谁,还不要动态分配内存——内核 I/O 队列、网络包缓冲、音频管线、线程间通信层,全是它20。zlocate 前身的原子实现:4 个槽,每槽一个 AtomicU32 值 + 一个 AtomicBool 占用标志,生产消费各自维护原子下标,满槽等空、空槽等满,无锁21。

2.6 每线程一个分配器:消灭跨线程的堆竞争

第 6 章的内存监控服务升级为多监听地址(每个 IP 一个监听线程)。第一版把同一个 DebugAllocator 传给所有线程——书主动承认这不高效,因为多线程共享一个分配器,内部要靠互斥锁保证一致,还要付出缓存一致性流量。改进版:serveLoop() 内部各自声明 DebugAllocator(.{ .thread_safe = false })——每个线程自己的堆,分配器根本不跨线程边界,内部锁全省;之上再叠一个每连接的 arena,连接一断 arena.deinit() 一次性回收全部会话内存,防慢燃泄漏、保底层堆紧凑22。这是第 2、3 章分配器概念的第一次实战合成。

2.7 Io.Group:0.16 的线程池替代品

std.Thread.Pool 在 0.16 被移除了——事件驱动 std.Io 并发模型的一部分。替代品 std.Io.Group 不需要分配器、不需要任务数、没有 init/deinit 生命周期:零值声明、group.async(io, func, .{args}) 投任务、group.await(io) 等全部完成,defer group.cancel(io) 兜早退。三行顶一个线程池。注意一个语义变化:任务是由事件循环调度的协程,从不挂起 I/O 的纯 CPU 函数会一直跑到完,所以 work() 不再需要 io 参数23。实测:3、5、7 三个增量各累加 10 万次,得 300000/500000/700000,乱序出现24。

2.8 zlocate:Scatter-Gather 模式的全貌

收尾项目是一个并行文件索引器。骨架是 Scatter-Gather 模式:一个协调者把大任务(爬整个文件系统)拆成小任务(目录),撒给一池工人(scatter);工人的结果回流到共享结构,合并成最终输出(gather)25。实测:14 个线程 0.26 秒爬完 13324 个目录、25908 个条目26。

并发的心脏是 WorkQueue.pop()——一个并行 BFS 不变量的实现27:

pop():  拿锁 →
    队列非空 → active += 1,弹出一个目录给调用者
    队列空 且 active == 0 → broadcast(全员收工!),返回 null
    队列空 但有 active  → cond.wait(睡死,等别人推新工作或喊收工)
finish(): 拿锁,active -= 1;active 归零且队空 → broadcast

图说:active 计数回答「整个索引什么时候算完」——它不为零,就说明还有工人在某个目录里、可能正往里推新的子目录(队列瞬间变零是假象)。finish() 放在 defer 里、crawlDirectory() 之后,保证槽位在整个爬取期间(包括推子任务)都占着 active,杜绝中途误报收工28。爬目录时单个受限文件夹(AccessDenied)只记一条 debug 日志、原子计数加一,绝不停掉整个索引29。

持久化层也有两个系统编程的好习惯。写:先写 .tmp 再 rename 原子替换——崩溃中途也留不下半个损坏的数据库;头 24 字节固定(ZLOC 魔数 4 + 版本 4 + 时间戳 8 + 条目数 8),零条目时文件也是 24 字节30。读:一次性把整个文件读进内存再解析,校验魔数和版本后才分配;搜索是惰性迭代器——不预建匹配列表,内部游标走到哪算哪,百万级条目的库内存占用也低31。

3. 加厚:两组对照与一条工程纪律

3.1 原子单槽的账,书上自己算的

原子版生产者-消费者的代码注释是全章最诚实的注脚:双线程都在忙等(自旋加 10 微秒睡眠),所以并不比条件变量版快——条件变量让线程睡死等信号,零轮询;原子给的是细粒度控制,用错地方(单槽忙等)就是用简单性换更高的 CPU 占用和更低的吞吐33。注释还点名了正解的形状:std.Thread.Mutex + 条件变量。

3.2 ring buffer 与单槽的对比账

同一章两版生产者-消费者,价值差在槽位数:单槽缓冲任何时刻只有一件在途,生产消费几乎锁步;环形缓冲(4 槽)让最多 4 件同时在途,实测输出里生产者连发 4 个(1–4 落在 0–3 槽)消费方才追上——缓冲吸收了两边速率的差34。书上把环形缓冲的适用范围说得很普世:内核 I/O 队列、网络包缓冲、音频管线、线程间通信,「速度不同的两个部件要交换数据,谁也别阻塞谁,还不要动态分配」的场景全是它35。

3.3 并发日志的临界区纪律

threadLog() 是「缩小临界区」的教科书:先在本地栈缓冲里用 bufPrint 把消息格式化完(字符串格式化是 CPU 密集活),再进临界区只做一次 writeStreamingAll——多个线程可以同时在锁外准备各自的消息,处理器核心保持忙碌;锁内只剩一次系统调用,互等时间压到最低36。另一个容易漏的对比:文件句柄按值传(它只是个轻量内核句柄),互斥锁必须按指针传——每个线程拷一把私锁就等于没锁37。

4. 作者的判断与证据

  • 判断: 「竞争条件与死锁不只是要躲开的障碍,它们是信号——系统的部件耦合得太紧了」;按「独立的、通信的实体」思考,软件才能随硬件优雅扩展32。这是全章的价值观落点。
  • 判断: 原子单槽忙等不优于条件变量——「原子和条件变量之间,选条件变量做清晰可维护的同步」19。证据:代码注释里自己承认单槽自旋版 CPU 更高、吞吐更低。
  • 判断: 固定栈数组(每线程分配器 + 每连接 arena)比共享全局堆更可预测22。证据:机制推演(免锁、免一致性流量),没有基准数字——这是论证较弱的一处。
  • 判断: zlocate 的 14 线程 0.26 秒26。证据:实测,但没有和单线程版对照——加速比本身没给,这是数据缺口。

4. 边界与局限

  • 并发 ≠ 并行的讨论停留在概念层,没有「同一任务串行 vs 并行」的对照基准。
  • RwLock 只演示了语义,没有读多负载下的吞吐基准(书自己声明:负载不忙时无差别)7。
  • 每线程分配器一节承认未测性能差异;Io.Group 的「协程而非 OS 线程」在不同 Io 后端(Threaded vs io_uring)下行为不同,纯 CPU 任务在 evented 后端会阻塞事件循环——注释里自己标了这条警告23。
  • zlocate 的活跃计数方案要求「发现」和「处理」在同一进程;无限深的符号链接循环靠排除列表(.git、node_modules)和用户配置兜底,没有 inode 去重。
  • 条件变量版的缓冲边界(满/空)处理正确,但没有演示多生产者多消费者的压力场景。

5. 可带走的

  1. 并发是结构、并行是同时执行;设计期就拆独立部件,别拆成争一个变量的碎片。
  2. 互斥锁守任意复杂的不变量;RwLock 读多写少时消灭读的串行化;两者都会阻塞,锁外能干的活别拿进临界区(格式化字符串是教科书例子)。
  3. counter += 1 不是原子的:读-改-写三步必须合成一条(fetchAdd/@atomicRmw),拆成 load+store 就有丢更新窗口。
  4. 内存序不是可有可无:.release 写发布、.acquire 读订阅,配对才有 happens-before;弱内存模型(ARM)上不配对就是错。
  5. 丢了几个?sort | uniq -d 找重复值——日志行数和最终计数器对不上,就是覆盖的指纹。
  6. 无锁设计三条件:每线程独占写槽、依赖事先已知、release/acquire 配对;满足不了就别硬上。
  7. 生产者-消费者:单槽+原子能跑但费 CPU;条件变量让线程睡死等信号,空闲零消耗,多数应用选它。
  8. 环形缓冲:速度不同的两部件交换数据、免动态分配——内核队列、音频管线全是它;取模回绕 + 每槽占用标志是无锁版的全部机关。
  9. 每线程一个 thread_safe = false 的分配器,再叠每连接 arena——跨线程堆竞争整个消失。
  10. Scatter-Gather + 「active 计数 + 条件变量」的并行 BFS 收工判定,是并行爬虫/索引器的通用骨架。

6. 原文地图

主题 原书章 原文位置
并发≠并行 Chapter 8: Orchestrating Concurrency and Synchronization text/89-ch08-chapter-8-orchestrating-concurrency-and-synchron.txt:11(搜「not always that simple」)
互斥锁保证 2000 Sharing memory safely: mutexes and RwLocks text/90-fm-sharing-memory-safely-mutexes-and-rwlocks.txt:120(搜「reliably 2000」)
RwLock 读写分治 Using RwLock text/91-fm-using-rwlock.txt:11(搜「simultaneously」)
读者自由交错实录 Using RwLock text/91-fm-using-rwlock.txt:134(搜「Reader 1 sees counter = 1000」)
无锁三条件 Avoiding race conditions by design text/92-fm-avoiding-race-conditions-by-design.txt:257(搜「writes exclusively」)
release/acquire 的正式保证 Avoiding race conditions by design text/92-fm-avoiding-race-conditions-by-design.txt:289(搜「happens-before」)
原子操作定义 Atomics: when to use them text/93-fm-atomics-when-to-use-them.txt:7(搜「indivisible」)
load+store 的丢更新窗口 Atomics: when to use them text/93-fm-atomics-when-to-use-them.txt:181(搜「two separate steps」)
4 线程 40 万次精确 Changing a variable using atomic operations text/94-fm-changing-a-variable-using-atomic-operations.txt:111(搜「400000」)
丢更新实录 2499996 Writing without atomic operations text/96-fm-writing-without-atomic-operations.txt:73(搜「2499996」)
uniq -d 揪重复 Writing without atomic operations text/96-fm-writing-without-atomic-operations.txt:76(搜「uniq」)
atomicRmw 修正版 Writing with atomic operations text/97-fm-writing-with-atomic-operations.txt:20(搜「atomicRmw」)
每线程分配器免锁 Using a separate memory allocator for each thread text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:156(搜「thread_safe = false」)
每连接 arena 回收 Using a separate memory allocator for each thread text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:192(搜「reclaims」)
Io.Group 三行替代线程池 Using a separate memory allocator for each thread text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:216(搜「removed in Zig 0.16」)
单槽原子的自我批评 Implementing the Producer-Consumer pattern with atomics text/99-fm-implementing-the-producer-consumer-pattern-with-.txt:18(搜「NOT faster」)
条件变量睡死等信号 Implementing Producer-Consumer with condition variable text/100-fm-implementing-producer-consumer-with-condition-va.txt:17(搜「block」)
5 产 3 收实录 Implementing Producer-Consumer with condition variable text/100-fm-implementing-producer-consumer-with-condition-va.txt:149(搜「Produced: 1」)
环形缓冲解决什么问题 Implementing Producer-Consumer with condition variable text/100-fm-implementing-producer-consumer-with-condition-va.txt:248(搜「different speeds」)
Scatter-Gather 两阶段 The Scatter-Gather pattern (parallelism) text/101-fm-the-scatter-gather-pattern-parallelism.txt:17(搜「scatter phase」)
pop() 并行 BFS 不变量 Implementation details text/102-fm-implementation-details.txt:17(搜「parallel-BFS」)
finish 防误报收工 Implementation details text/102-fm-implementation-details.txt:158(搜「prevents a false termination」)
原子写+rename 持久化 Implementation details text/102-fm-implementation-details.txt:272(搜「atomic write pattern」)
惰性搜索迭代器 Implementation details text/102-fm-implementation-details.txt:354(搜「lazy」)
zlocate 0.26 秒实录 Using the zlocate utility text/103-fm-using-the-zlocate-utility.txt:60(搜「0.26s」)
24 字节空库头 Using the zlocate utility text/103-fm-using-the-zlocate-utility.txt:157(搜「ZLOC」)
章末价值观 Using the zlocate utility text/103-fm-using-the-zlocate-utility.txt:221(搜「too tightly coupled」)

本章拆解依据原书第 8 章(含各小节)。每条出处可用 node scripts/book-verify.mjs systems-programming-with-zig 回核。


  1. 出处:「Chapter 8: Orchestrating Concurrency and Synchronization」(text/89-ch08-chapter-8-orchestrating-concurrency-and-synchron.txt:21,搜「far from the truth」)。 ↩

  2. 出处:「Chapter 8: Orchestrating Concurrency and Synchronization」(text/89-ch08-chapter-8-orchestrating-concurrency-and-synchron.txt:37,搜「design phase」)。 ↩

  3. 出处:「Avoiding race conditions by design」第 315 段(text/92-fm-avoiding-race-conditions-by-design.txt:315,搜「serializing access entirely」)与第 325 段(text/92-fm-avoiding-race-conditions-by-design.txt:325,搜「tradeoff is scope」)。 ↩

  4. 出处:「Sharing memory safely: mutexes and RwLocks」第 120 段(text/90-fm-sharing-memory-safely-mutexes-and-rwlocks.txt:120,搜「reliably 2000」)。 ↩

  5. 出处:「Using RwLock」第 11 段(text/91-fm-using-rwlock.txt:11,搜「simultaneously」)。 ↩

  6. 出处:「Using RwLock」第 134 段(text/91-fm-using-rwlock.txt:134,搜「Reader 1 sees counter = 1000」)与第 186 段(text/91-fm-using-rwlock.txt:186,搜「take turns」)。 ↩

  7. 出处:「Using RwLock」第 21 段(text/91-fm-using-rwlock.txt:21,搜「not that busy」)。 ↩↩

  8. 出处:「Avoiding race conditions by design」第 22 段(text/92-fm-avoiding-race-conditions-by-design.txt:22,搜「sentinel」)与第 243 段(text/92-fm-avoiding-race-conditions-by-design.txt:243,搜「one microsecond」)。 ↩

  9. 出处:「Avoiding race conditions by design」第 289 段(text/92-fm-avoiding-race-conditions-by-design.txt:289,搜「happens-before」)与第 291 段(text/92-fm-avoiding-race-conditions-by-design.txt:291,搜「weak memory」)。 ↩

  10. 出处:「Atomics: when to use them」第 7 段(text/93-fm-atomics-when-to-use-them.txt:7,搜「indivisible」)与第 9 段(text/93-fm-atomics-when-to-use-them.txt:9,搜「bank account」)。 ↩

  11. 出处:「Atomics: when to use them」第 181 段(text/93-fm-atomics-when-to-use-them.txt:181,搜「two separate steps」)与第 187 段(text/93-fm-atomics-when-to-use-them.txt:187,搜「fetchAdd」)。 ↩

  12. 出处:「Writing with atomic operations」第 44 段(text/97-fm-writing-with-atomic-operations.txt:44,搜「failed to protect」)。 ↩

  13. 出处:「Changing a variable using atomic operations」第 111 段(text/94-fm-changing-a-variable-using-atomic-operations.txt:111,搜「400000」)与「Writing with atomic operations」第 70 段(text/97-fm-writing-with-atomic-operations.txt:70,搜「single, global order」)。 ↩

  14. 出处:「Writing with atomic operations」第 31 段(text/97-fm-writing-with-atomic-operations.txt:31,搜「out of order」)。 ↩

  15. 出处:「Using a separate memory allocator for each thread」第 412 段(text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:412,搜「generate data」)。 ↩

  16. 出处:「Implementing the Producer-Consumer pattern with atomics」第 18 段(text/99-fm-implementing-the-producer-consumer-pattern-with-.txt:18,搜「NOT faster」)。 ↩

  17. 出处:「Implementing Producer-Consumer with condition variable」第 17 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:17,搜「block」)。 ↩

  18. 出处:「Implementing Producer-Consumer with condition variable」第 149 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:149,搜「Produced: 1」)。 ↩

  19. 出处:「Using a separate memory allocator for each thread」第 448 段(text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:448,搜「maximum speed」)。 ↩↩

  20. 出处:「Implementing Producer-Consumer with condition variable」第 248 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:248,搜「different speeds」)。 ↩

  21. 出处:「Implementing Producer-Consumer with condition variable」第 264 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:264,搜「lock-free ring buffer」)。 ↩

  22. 出处:「Using a separate memory allocator for each thread」第 156 段(text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:156,搜「thread_safe = false」)与第 192 段(text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:192,搜「reclaims」)。 ↩↩

  23. 出处:「Using a separate memory allocator for each thread」第 216 段(text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:216,搜「removed in Zig 0.16」)。 ↩↩

  24. 出处:「Using a separate memory allocator for each thread」第 358 段(text/98-fm-using-a-separate-memory-allocator-for-each-threa.txt:358,搜「300000」)。 ↩

  25. 出处:「The Scatter-Gather pattern (parallelism)」第 17 段(text/101-fm-the-scatter-gather-pattern-parallelism.txt:17,搜「scatter phase」)。 ↩

  26. 出处:「Using the zlocate utility」第 60 段(text/103-fm-using-the-zlocate-utility.txt:60,搜「0.26s」)。 ↩↩

  27. 出处:「Implementation details」第 17 段(text/102-fm-implementation-details.txt:17,搜「parallel-BFS」)。 ↩

  28. 出处:「Implementation details」第 158 段(text/102-fm-implementation-details.txt:158,搜「prevents a false termination」)。 ↩

  29. 出处:「Implementation details」第 180 段(text/102-fm-implementation-details.txt:180,搜「AccessDenied」)。 ↩

  30. 出处:「Implementation details」第 272 段(text/102-fm-implementation-details.txt:272,搜「atomic write pattern」)与「Using the zlocate utility」第 157 段(text/103-fm-using-the-zlocate-utility.txt:157,搜「ZLOC」)。 ↩

  31. 出处:「Implementation details」第 354 段(text/102-fm-implementation-details.txt:354,搜「lazy」)。 ↩

  32. 出处:「Using the zlocate utility」第 221 段(text/103-fm-using-the-zlocate-utility.txt:221,搜「too tightly coupled」)。 ↩

  33. 出处:「Implementing the Producer-Consumer pattern with atomics」第 18 段(text/99-fm-implementing-the-producer-consumer-pattern-with-.txt:18,搜「NOT faster」)。 ↩

  34. 出处:「Implementing Producer-Consumer with condition variable」第 528 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:528,搜「rapidly filling」)。 ↩

  35. 出处:「Implementing Producer-Consumer with condition variable」第 252 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:252,搜「kernel I/O queues」)。 ↩

  36. 出处:「Implementing Producer-Consumer with condition variable」第 609 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:609,搜「CPU-intensive」)。 ↩

  37. 出处:「Implementing Producer-Consumer with condition variable」第 597 段(text/100-fm-implementing-producer-consumer-with-condition-va.txt:597,搜「isolated copies」)。 ↩