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)与并发正确性
并发正确性通常分为两类:
其中线性化(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 并发中的“卡住”和“卡壳”:进度性误区速记
容易混淆的点是:
- “看起来线程卡住了”不一定意味着系统卡死。无锁关注的是全局前进。
- “单线程在隔离环境下能完成”也不等于在高竞争下能持续推进。
- 进度性指标越强,误解越少,但实现成本越高。