1 概述与术语界定

无锁数据结构(Lock-free data structures)指在多线程或多进程环境中,通过原子操作协同并发访问,从而减少对传统互斥锁(mutex)的依赖。其目标通常是:在高竞争条件下维持较高吞吐,并降低因锁竞争造成的阻塞与调度延迟。

“无锁”并不意味着系统完全不会发生等待现象。更准确的表述是:算法层面不使用互斥锁来保证互斥,从而避免一线程持锁导致其他线程长期阻塞的模式。

1.1 无锁、无等待与阻塞并发的区别

  • 阻塞并发(Blocking:线程可能因为获取锁、等待条件或资源而被挂起,进度取决于调度与持锁线程是否继续运行。常见例子是互斥锁与条件变量
  • 无锁并发(Lock-free):系统保证“至少一个线程会在有限步内继续前进”。其他线程可能反复失败重试,但不会出现“所有线程都卡死在同一个局部循环”的系统性风险(在标准模型假设下)。
  • 无等待(Wait-free):对每个线程都提供“在有限步内完成操作”的保证。它更强,但实现难度与成本通常更高。
  • 障碍(Obstruction-free):当某个线程能连续运行且不被其他线程干扰时,它能在有限步内完成。它更弱,可能在高竞争下表现不稳定。

1.2 进度性指标:Lock-free / Wait-free / Obstruction-free

进度性(liveness)用于描述并发执行是否会继续产生可观察的进展。不同指标的强弱关系常被总结为: Wait-free(最强) ≥ Lock-free(中强) ≥ Obstruction-free(较弱)。

  • Lock-free强调“全系统层面的前进”,允许个别线程长期饥饿(starvation),但不会让系统整体停摆。
  • Wait-free强调“个体线程的确定完成时间(有限步)”,对实时性或特定延迟敏感场景更有吸引力。
  • Obstruction-free强调“去除竞争后的可终止性”,在竞争不激烈时可能足够,但在争用高时无法提供同等级别的整体推进保证。

1.3 线性化(Linearizability)与并发正确性

并发正确性通常分为两类:

  • 安全性(safety):不会产生违背数据结构抽象语义的结果,例如队列不可能“凭空失去元素”、栈不可能返回不存在的值。
  • 进度性(liveness):不会出现系统整体无法前进的情况(对应上节指标)。

其中线性化(Linearizability)是一种常用的强一致性定义:每个并发操作都可视作在某个“线性化点”瞬间生效,从而得到与某个合法顺序执行一致的结果。证明线性化通常是无锁算法正确性的核心之一。


2 并发硬件与原子原语基础

无锁算法之所以可行,依赖底层硬件提供的原子操作与可控的内存可见性规则。算法设计者必须理解:哪些读写是原子的,哪些顺序会被重排,以及需要使用何种屏障来约束执行。

2.1 原子操作的类别:CAS、交换、加法等

常见原子原语包括:

  • CAS(Compare-And-Swap):比较某位置值是否等于期望值,若是则用新值替换;返回是否成功。无锁链表、栈与队列中非常常见。
  • FAA(Fetch-and-Add)/加法类原语:以原子方式获取并更新计数或索引,常用于无锁队列的索引分配或计数器。
  • 交换(Swap):以原子方式用新值覆盖旧值并返回旧值,适合某些标志位或单次置换场景。
  • 原子读-改-写(RMW):泛指上面这类需要原子性保障的复合操作。

原子操作的关键在于:它们在并发下体现为“不可拆分”的一步,从而允许算法使用失败重试与状态机推进来形成一致行为。

2.2 内存模型与可见性:顺序一致性与弱一致性影响

硬件与编译器可能对指令重排进行优化。内存模型定义了不同线程观察内存读写的顺序关系。常见概念包括:

  • 顺序一致性(Sequential Consistency:提供最直观的模型,读写效果如同按某个全局顺序发生。
  • 弱一致性(Weak Consistency):允许更激进的重排与可见性延迟。此时,若算法只依赖“代码顺序”,就可能出现难以复现的错误。

因此,无锁算法常需要利用原子操作附带的内存语义(如 acquire/release 或等价语义)来保证:当一个线程成功“发布”节点或指针后,其他线程在“看到指针变化”的同时也能看到该节点的完整初始化结果。

2.3 指令级实现要点:伪共享、缓存一致性、屏障

无锁算法的性能不仅取决于算法复杂度,也受硬件微架构影响:

  • 伪共享(false sharing):不同线程更新位于同一缓存行内的变量,导致缓存行在核心间频繁失效,从而拖慢 CAS 等操作。
  • 缓存一致性:原子 RMW 往往触发缓存一致性协议,竞争越激烈,代价越高。
  • 内存屏障(fence/barrier):用于约束读写顺序,保证跨线程可见性。但屏障过多会显著增加延迟。

工程上通常会结合对齐(alignment)与数据布局,减少热点字段的争用范围。


3 无锁算法通用范式

无锁算法并非单一套路,而是围绕“原子状态推进 + 重试 + 协作”的组合展开。许多经典无锁结构可视为在不同抽象层复用同一套并发范式。

3.1 自旋重试与乐观并发控制

典型操作流程是:

1 概述与术语界定

2 并发硬件与原子原语基础

3 无锁算法通用范式

4 经典问题与关键技术

这是一种乐观并发控制:假设冲突较少或冲突发生后能够快速重试。若竞争过高,自旋失败次数会增长,吞吐下降,需要与退避策略或结构设计共同配合。

3.2 版本化与标记指针(Tagged Pointer)

并发场景下,“看起来值没变”但实际上经历过变化,会让 CAS 判断失真,这引出 ABA 问题(后文详述)。常见应对之一是:

  • 版本号:将指针与计数或版本组合,每次更新都改变版本。
  • 标记位(tag/mark bit):在指针的高位或低位携带状态,例如“逻辑删除”标记。

Tagged Pointer把“地址”和“状态”绑定在一个原子可比较对象上,从而提高一致性判断能力

3.3 帮助机制(Helping)与协作推进

无锁算法常包含“帮助完成他人操作”的设计:当线程在遍历结构时发现目标节点处于某种中间态(例如被标记为删除但未完全链接),它可以执行补全步骤。这样做的意义是:

  • 缩短中间态停留时间;
  • 避免某个失败线程长期遗留结构在不一致阶段;
  • 提升整体推进的概率,从而增强进度性。

帮助机制让算法更接近“全系统协作推进”的目标,而非依赖单线程不断重试。

3.4 并发中的状态机思想:冻结/标记/完成

许多无锁算法可抽象为节点生命周期状态机,例如:

  • 冻结(freeze):暂时阻止某些操作继续基于旧状态修改(具体形式依算法而定)。
  • 标记(mark):用原子可见的方式标记节点的逻辑状态,如“待删除”“不可见”等。
  • 完成(complete):执行实际的结构重连或回收前的最后阶段。

通过把复杂操作拆为可验证的原子步骤,并为每一步定义可观察的状态,算法更容易证明线性化点并建立不变量


4 经典问题与关键技术

无锁算法的难点常集中在少数“系统性问题”上:ABA、竞争窗口、取消语义等。正确的技术选择往往决定算法是否可证明、是否能在真实并发下稳定运行。

4.1 ABA 问题及应对:版本号、双宽 CAS、标记位

ABA 问题描述如下情形:某线程读到指针值为 A,随后其他线程将其改为 B,再改回 A。CAS 以为“值从未变过”,从而错误地通过比较,导致链路或结构出现逻辑错误。

常见应对策略:

  • 版本号/计数器:每次更新都改变版本,使得“回到 A”也会变成“(A, 新版本)”。
  • 双宽 CAS(double-width CAS):一次原子比较-更新更大的组合字段(例如指针+版本)。
  • 标记位:将删除或状态转换信息编码在指针标志中,使回跳无法伪装成一致。

4.2 竞争窗口与一致性维护

无锁算法常通过减少竞争窗口来保持一致性:

  • 在进入关键更新前先捕获必要的上下文(例如前驱/后继节点)。
  • 在 CAS 失败后重新读取并验证上下文仍有效。
  • 对“可能过期的读取结果”保持警惕,尤其是遍历过程中指针可能被其他线程改变或被逻辑删除。

一致性维护并不意味着零冲突,而是要求:在每一次成功原子提交后,数据结构抽象语义仍成立,线性化点可被合理定位。

4.3 自旋退避策略与性能调优

自旋在短期竞争下有效,但冲突增大时会浪费 CPU。退避策略通常包括:

  • 量化重试(短暂停顿、yield 等)
  • 指数退避或基于随机的延迟
  • 将失败后的流程与结构设计耦合(例如尽早帮助完成他人操作)

性能调优还要考虑:在不同负载下最佳策略可能不同,因此通常依赖基准测试与参数调节。

4.4 取消与超时语义(如何在无锁语义下实现)

传统锁的取消语义较直观:线程等待锁时可以中断或超时。无锁中操作往往由重试循环构成,取消的难点在于:撤销一个正在进行的原子更新并不总是可行。

常见做法是采用语义层面的折中:

  • 取消前检查:在重试循环的合适位置检测取消标志,若取消则返回错误或特定结果。
  • 有限步尝试:将无限重试改为有限尝试次数(接近“可终止但不保证无等待”),超出则返回失败。
  • 与调用方契约:明确“取消不回滚已完成的结构变更”,避免引入额外一致性负担。

5 常见无锁数据结构

无锁数据结构通常围绕基础抽象(栈、队列、链表、哈希等)展开。它们在实现上差异很大,但往往复用:CAS 更新指针、逻辑删除、帮助机制与线性化点推理等通用技术。

5.1 无锁栈(Lock-free Stack)

无锁栈常用“基于链表的头指针 CAS”实现:

  • push:新节点准备好后,反复尝试把它插入到头部。
  • pop:反复尝试读取头节点并用 CAS 将头指针跳到下一个节点。

为处理删除与 ABA 风险,可能需要版本化指针或标记逻辑删除。许多实现还会配合内存回收机制,避免回收导致的悬空引用

5.2 无锁队列(Lock-free Queue)

无锁队列常见代表思路是 Michael-Scott 型队列(后文概览)。其基本要点是:

  • 使用头指针与尾指针并通过原子 CAS 更新;
  • 允许部分延迟更新(例如尾部可能稍慢于实际队列末端),但通过帮助机制最终修正
  • 通常需要哨兵节点(dummy/sentinel)简化空队列与边界情况。

5.3 无锁链表(Lock-free Linked List)

无锁链表更复杂的原因在于“删除”操作:

  • 删除常采用逻辑删除标记(让节点先不可被正常遍历到),随后再做物理移除(重连指针)。
  • 遍历过程中遇到被标记节点时,可能会触发帮助修复链接。

链表的线性化点通常与“标记成功”或“成功重连”有关,具体取决于操作语义定义。

5.4 无锁哈希表与分段/桶式设计

无锁哈希表往往采用分桶(bucket)或分段(segment)来降低争用:

  • 在桶级别用更细粒度的无锁结构(例如桶内链表或小型无锁映射)来保存键值。
  • 扩容(resizing)是难点,常见策略是分阶段迁移、双表协同或使用额外的元数据来指示元素归属。

由于扩容带来的可见性与一致性复杂度,无锁哈希表在工程上通常比无锁队列更“重”。

5.5 无锁集合与映射(Set/Map)抽象

集合与映射通常把“查找/插入/删除”的抽象映射到无锁链表或无锁树状结构的节点操作上。关键点包括:

  • 删除的线性化点如何定义(逻辑删除是否即为“删除生效”)
  • 插入与删除之间的竞争如何避免重复键或错误可见性
  • 与内存回收机制的协同,确保删除后的节点不会被误访问

在可证明性与工程实现之间,很多库会选择较成熟的无锁映射组件组合而非从零构造。

5.6 无锁栈/队列的实践选择:Michael-Scott 等思路概览

在无锁队列上,实践中常见的是采用“头尾指针 CAS + 哨兵节点 + 帮助更新尾指针”的结构化思路。无锁栈则常用“头指针 CAS + 版本化指针”的模式。

“实践选择”通常还取决于:

  • 目标语言与其原子/内存模型支持情况;
  • 是否需要高频取消(影响语义设计);
  • 内存回收成本(影响整体吞吐)。

6 并发内存回收(Reclamation)

无锁算法的共享数据结构会频繁创建与移除节点。若不恰当回收,可能出现两类严重问题:悬空指针(dangling pointer)与内存泄漏。由于无锁并发缺少传统“谁持锁就保证没人用到”的时序保证,回收必须有专门机制。

6.1 为什么无锁需要专门的回收机制

无锁结构中,线程 A 可能正在遍历某节点,而线程 B 已经逻辑删除该节点并准备释放内存。若释放发生太早,线程 A 的读取将变为未定义行为。

回收机制的任务是:确保在某节点被释放前,所有可能仍持有该节点引用的线程都不再需要它。

6.2 Hazard Pointers(危险指针)

危险指针是一种常见方案。基本思想是:

  • 线程在访问某节点前,把该节点地址发布到自己的危险指针槽位;
  • 回收线程在释放节点前检查:是否存在其他线程声明“仍可能访问”该节点;
  • 若仍存在声明,则延迟回收,直到确认安全。

危险指针的优点是思路清晰、可控性强;缺点是需要额外维护槽位并在回收时进行扫描,可能带来开销。

6.3 Epoch-based / 计时代回收(如 EBR/RCU 思想)

计时代(epoch)回收把时间划分为若干“时代”。线程进入某时代后,回收者记录节点的“退休时代”。当确认所有线程都已跨过该时代后,才安全释放节点。

这类方法的核心优势是:回收时不必频繁全局扫描危险指针;代价是需要管理线程的进展(例如线程是否卡在某处不退出时代),否则会延迟释放。

6.4 引用计数与延迟回收权衡

引用计数(reference counting)通过计数来判断何时安全释放节点。并发场景下,引用计数的原子更新带来额外成本;并且也存在“环引用”等需要额外处理的问题。

无锁算法常见做法是:要么使用更合适的回收机制(hazard pointers 或 epoch),要么把引用计数设计为尽量减少原子开销并结合延迟回收策略。

6.5 内存屏障与回收时序的正确性

回收正确性不仅是“何时释放”,还包括“何时发布可见性”。当一个线程完成节点初始化并把指针发布给其他线程时,需要相应的内存语义保证初始化对读取方可见;在回收流程中,发布危险指针或进入 epoch 时代的动作也需要顺序约束,避免出现“看似发布了引用但实际上读取方尚未能稳定观察”的竞态。


7 正确性证明方法与验证

无锁算法的正确性证明往往比实现更“硬核”。它通常围绕线性化、不可变式和进度证明展开,并可能借助模型检验与形式化工具。

7.1 线性化点的识别与证明框架

证明线性化通常要求:

1 概述与术语界定

2 并发硬件与原子原语基础

3 无锁算法通用范式

识别线性化点是难点,因为无锁结构中可能存在“中间状态”,需要谨慎定义“操作何时算完成”。

7.2 不变量(Invariants)与状态转移推理

不可变式用于约束数据结构在任意并发执行下的形态。例如队列应保持某种链接关系的正确性;链表应保持删除标记与可达性的一致性。

状态转移推理强调:每个原子步骤都应维护不可变式,且失败的步骤不会破坏结构的可证明性质。帮助机制(helping)也需要被纳入推理:帮助线程执行的补全操作同样必须维持不变量。

7.3 进度证明:保证“至少一个线程前进”

进度性证明通常以“全系统层面”或“在特定条件下”展开:

  • 指出导致停滞的典型原因(例如所有 CAS 都永远失败)。
  • 论证在竞争行为满足某些假设时,仍存在某个线程会成功提交关键步骤,从而产生结构变化。
  • 对于需要帮助完成的算法,证明“卡住的中间态会被别人推进”以避免死锁式僵局。

进度证明常与具体的算法结构强绑定,不能仅靠一般性口号。

7.4 模型检验与形式化验证工具概览

在复杂无锁算法上,人们常使用:

  • 模型检验:将抽象状态空间有限化,检查安全性/线性化等性质是否被违反。
  • 形式化验证:使用证明助手或专门逻辑框架对代码或抽象模型进行推导式证明。

这类方法的价值在于减少“证明漏掉某条竞态”的风险,但也受到状态爆炸与抽象精度的限制。


8 性能与工程实践

无锁算法的性能评估常以吞吐(throughput)、延迟(latency)、争用程度(contention)为核心维度。理论上的无锁优势并不总是自动转化为工程上的持续胜利。

8.1 吞吐、延迟与竞争程度的关系

竞争越激烈:

  • CAS 成功率可能下降;
  • 重试次数增加;
  • 线程之间互相影响导致延迟抖动变大。

在低竞争或短临界路径的场景,无锁往往更有优势;在高竞争、热点单点更新频繁的场景,锁可能因等待可预测性更好反而表现稳定。

8.2 争用热点:CAS 热点与扩展性

许多无锁结构都围绕某个热点字段(如队头/队尾指针)做原子更新。热点会导致:

  • 原子操作触发缓存一致性通信成本;
  • 多线程争抢导致成功率降低;
  • 扩展性受限。

工程上常通过分片、分层结构或减少共享更新频率来缓解。

8.3 伪共享、对齐与缓存行为

通过对齐与数据布局优化可以降低伪共享:

  • 把频繁更新的计数器与共享标志隔离到不同缓存行;
  • 减少不必要的共享字段;
  • 选择合适的内存布局以改善局部性。

这些优化在无锁场景下尤其关键,因为原子操作对缓存行为高度敏感。

8.4 回收机制的开销对性能的影响

内存回收策略本身也会带来成本:

  • hazard pointers 需要维护槽位并在回收时扫描;
  • epoch 回收依赖线程进展,可能导致延迟释放与更高内存占用;
  • 额外屏障与元数据维护也会影响临界路径延迟。

因此性能评估必须将“回收开销”纳入整体,而不是只看数据结构操作本身。

8.5 调参与基准测试:压力测试与可重复性

工程实践通常包含:

  • 压力测试:在不同线程数、不同负载分布下测量稳定性;
  • 可重复性:固定随机种子或控制环境变量,避免性能结论漂移;
  • 观察指标:不仅看平均吞吐,还要看尾部延迟(p99 等)。

无锁算法的性能可能对细节参数敏感,建议逐项验证瓶颈来源。


9 与其他并发策略的比较

无锁并不是通用解。与锁、STM、乐观并发控制等策略对比,有助于在不同需求下做出合理选择。

9.1 与锁(Mutex/Spinlock)的权衡

  • 锁的优点:实现相对直接,语义清晰,可提供可控的临界区与等待机制。
  • 锁的缺点:持锁线程延迟会造成阻塞;在高竞争下可能产生抖动与上下文切换成本。
  • 无锁的优点:避免传统阻塞模式,整体推进更可能持续。
  • 无锁的缺点:复杂性更高,证明与调试成本大;竞争激烈时重试可能吞噬性能。

实际选型需考虑负载、延迟目标、实现与维护成本。

9.2 与软件事务内存(STM)的对比

STM把多个读写封装为事务,依赖冲突检测与重试来保证一致性。无锁则通常针对特定数据结构实现原子更新与线性化点。对比上:

  • STM 提供更通用的组合性,但可能引入更大运行时开销;
  • 无锁更贴合单个结构的语义,性能潜力高,但扩展到复杂跨结构事务时实现困难。

9.3 与乐观并发控制的关系

无锁本质上常带有乐观重试:假设并发冲突可被快速检测并处理。与一般“乐观并发控制”(如版本校验+重试事务)相比,无锁通常把关键更新限定到少量原子步骤,因此能在更底层获得确定一致性点与可证明的结构约束。

9.4 与“近似无锁/细粒度锁”的边界

有些系统宣称“无锁”但实际仍使用细粒度锁、或把关键路径改写成较低争用的互斥方式。这类实现可能在某些负载下表现接近无锁,但在理论进度保证上不一定满足 Lock-free 或 Wait-free 的严格定义。

工程上应关注:是否存在阻塞路径、是否能给出进度性保证、取消语义是否与需求匹配。


10 编程语言与库生态

不同编程语言对原子操作、内存模型和指针表示的支持差异,会直接影响无锁算法的可实现性与安全性。

10.1 内存模型差异:Java/C++/Rust 的影响概览

  • Java提供较明确的内存模型语义,原子类与 volatile/cas 相关操作带有特定的可见性约束。
  • C++的原子库(std::atomic)提供精细的内存序语义,但也对开发者提出更高要求。
  • Rust强调类型与所有权安全,但仍需通过原子与 unsafe 边界处理无锁结构中的指针与回收细节。

语言差异主要体现在:你需要怎样表达“发布”和“获取”的顺序,以及回收机制如何与生命周期管理对齐。

10.2 原子类型与指针操作接口

无锁实现通常需要:

  • 原子指针或原子整数(含位打包如 tagged pointer)
  • CAS 循环与原子更新接口
  • 与内存屏障/内存序关联的参数

此外,部分结构需要对指针做位操作(如标记位),这依赖语言对指针表示与对齐的保证。

10.3 常见库与实现风格(接口层面的差异)

库的差异可能体现在:

  • 接口是否提供明确的返回语义(例如 pop 在空时返回 Option/Result)
  • 是否内置特定回收方案(hazard pointers 或 epoch)
  • 是否提供批量操作或迭代器接口(迭代器在无锁场景更难保证一致性)

因此选库时不仅看性能指标,还要看语义契约是否与业务需求一致。

10.4 与 GC(垃圾回收)环境的协同策略

在具备垃圾回收的环境中,无锁回收问题会部分缓解:被删除节点的内存可能不会立刻释放,从而减少悬空指针风险。但无锁算法仍可能需要逻辑删除与内存占用管理;同时 GC 暂停、写屏障与对象可达性维护也会引入新的性能权衡。

因此,“有 GC ≠ 无需并发正确性设计”,而是把回收策略的一部分风险转移到运行时。


11 安全性、可维护性与常见陷阱

无锁代码容易出现“表面正确、偶发崩溃”的问题。除了算法本身,还需要工程流程配合:测试、日志、降级策略与清晰文档。

11.1 正确性脆弱点:ABA、回收时序、线性化偏差

常见脆弱点包括:

  • ABA 未处理:版本号或标记不充分导致 CAS 误判。
  • 回收时序错误:hazard/epoch 协议与内存语义不匹配,导致释放过早或释放延迟过久。
  • 线性化点定义偏差:操作“何时生效”的约定与实现不一致,导致一致性测试失败。

这些问题往往与竞态相关,且对特定时序高度敏感。

11.2 调试难题:并发时序不可复现

无锁算法的调试困难来自:

  • 失败路径依赖时序与调度;
  • 重试循环可能在测试环境下“恰好不触发”;
  • 观察探针(logging/断点)本身会改变时序,掩盖问题。

实践中常需要使用压力测试、记录关键事件、或借助更强的并发测试框架。

11.3 容错与降级策略:何时退回锁或其他方案

当需求允许时,可以引入降级:

  • 在检测到异常高竞争或异常重试次数后,切换到锁或细粒度策略;
  • 对取消/超时场景采用更明确的失败返回;
  • 对极端负载下的系统稳定性优先于峰值吞吐。

降级并不削弱无锁思想,反而提升整体可用性。

11.4 文档与测试策略建议

良好实践通常包括:

  • 明确记录操作语义、线性化点假设与线程安全契约;
  • 覆盖边界:空结构、删除冲突、并发插入/删除竞态;
  • 进行跨平台测试(不同 CPU/内存模型差异可能暴露隐藏错误)。

12 应用场景与示例用法(偏概念)

无锁思路常出现在追求高吞吐、降低阻塞风险的系统中。具体选择取决于负载分布与回收成本。

12.1 高并发队列:任务调度与流水线

在任务调度或流水线处理中,队列往往位于关键路径。无锁队列可用于在多生产者多消费者场景中减少锁阻塞,使工作分发更平滑。

12.2 读多写少:结构选择与回收成本

读多写少的场景里,无锁结构的收益取决于:

  • 写操作的争用热点是否真的低;
  • 回收机制是否在写频率下仍可接受;
  • 读操作是否需要额外一致性保证(如快照或线性化读)。

若写很少,回收成本可控,无锁可能更合适。

12.3 实时系统中的无锁思路(以进度性为导向)

在实时或准实时环境中,“进度性保证”比平均吞吐更重要。无锁可作为降低长时间阻塞的手段,但若实现与回收延迟或高竞争重试导致尾部延迟不可控,则仍需要谨慎评估。

12.4 服务端中常见的无锁组件组合方式

服务端系统常把无锁组件用于局部关键路径,例如:

  • 任务队列(无锁队列)
  • 事件列表(无锁链表或集合)
  • 统计计数(原子计数器)

组件组合时要关注:不同组件的语义(线性化要求、取消策略、回收策略)能否在整体系统级别兼容。


13 梗与趣味理解(轻量)

无锁理论较抽象,引入轻量比喻有助于建立直觉,但仍需回到形式化语义理解其正确性与性能边界。

13.1 “不加锁也能排队”:无锁并发的直觉比喻

可以把无锁队列想象成“大家都在看同一张队伍名单”,但每次调整只靠原子一步确认。有人失败就不抱怨不排队,而是重新观察名单再试一次。失败不等于彻底停下,整体仍会有人把队伍推进下去。

13.2 CAS 自旋的“永不服输”体验(文化梗)

CAS 自旋常被形容为“永不服输”: 只要共享状态还没按预期变成目标值,就继续尝试。 这份“倔强”是算法哲学的一部分,但工程上也要记得:太多“倔强”会变成 CPU 消耗。

13.3 并发中的“卡住”和“卡壳”:进度性误区速记

容易混淆的点是:

  • “看起来线程卡住了”不一定意味着系统卡死。无锁关注的是全局前进。
  • “单线程在隔离环境下能完成”也不等于在高竞争下能持续推进。
  • 进度性指标越强,误解越少,但实现成本越高。