Skip to content

活锁

Livelock · 活跃锁

参与者持续响应并改变状态,却在无限执行中不再产生规格所要求完成事件的进展失败。

形式陈述

活锁描述一种无限执行:系统持续产生协议内部动作、冲突响应或重试步骤,但某类由规格指定的高层完成事件从某个位置起不再出现。设 I 为内部动作集合,C 为操作提交、请求返回或任务完成等目标事件;若执行后缀含有无穷多个 I 动作而没有 C 事件,并且这些内部动作不断改变下一步选择或共享状态,就出现了相对于 C 的活锁。

定义必须明确“什么算进展”。网络协议持续收发拒绝消息、事务不断检测冲突并回滚、两个线程反复改变礼让标志,都说明系统在活动;若用户请求始终不返回,这些活动对目标规格没有贡献。相反,后台维护循环可以无限运行,但只要前台操作照常完成,就不能仅凭“存在无限内部动作”判为系统活锁。

活锁通常破坏活性而不破坏安全性:每次回退都可能谨慎地避免冲突,状态从未进入非法区域,却也永远到不了完成状态。它不要求等待依赖图成环,也不要求线程阻塞。对称协议尤其容易出现此问题,因为相同观察会让参与者同时采取相同纠正动作,下一轮又恢复到对称冲突。

随机退避、确定性优先级、唯一身份破缺和竞争管理器都可以打破对称,但其保证强度取决于模型。随机化通常只给出概率一终止或期望时间界;固定优先级可能让高优先任务完成,却把问题转成低优先任务的饥饿。某个策略在常见调度下工作,不等于它在所有可容许执行中排除活锁。

直觉

死锁像两个人都站着不动等对方让路;活锁像两个人在窄门前同时向左、又同时向右,一直礼让却始终没有人通过。每一步都合理响应了当前冲突,失败来自响应之间过度同步,而不是参与者停止执行。

观察 CPU 使用率只能提供线索。活锁可能让 CPU 满载,也可能由低频定时重试构成;普通计算密集型任务同样可能满载却稳定完成工作。判据始终是把执行投影到规格关心的完成事件,看目标进展是否永久消失。

例子与边界

两个线程都采用礼让协议:发现对方也想进入临界区时,立即清除自己的意愿标志,等待片刻,再重新声明。若等待时长和调度完全对称,它们每轮都同时退出、同时重试,状态不断变化而无人进入。加入不同身份决定的优先级可确定谁先坚持;加入独立随机延迟则以高概率打破同步,但需要明确随机源和概率性终止含义。

乐观事务内存中,两个事务读取相同版本并同时准备提交,冲突检测让双方都回滚;若它们总以相同节奏重启,便可能无限回滚。事务代码在独占运行时本可提交,所以算法至少可能是obstruction-free;只有竞争管理器确保冲突中持续有事务完成时,组合系统才可能达到lock-free

CAS 重试中的单个线程可以无限失败,但若每次失败都意味着另一个操作成功提交,系统仍有全局完成事件,不能称为整体活锁。这是个体饥饿与系统活锁的分界。若所有线程只在冲突间互相使 CAS 失效,没有任何高层操作完成,才符合所选完成规格下的活锁。

推论与应用

排查活锁要记录“尝试—冲突—回退—重试”的状态转移以及高层完成计数,而非只抓取阻塞堆栈。线程转储可能显示每个线程都在运行,等待图也没有环;执行轨迹中的周期、对称动作和长期为零的提交率才是决定性证据。

修复时应先破除导致同步振荡的结构:给参与者稳定排序、让胜者保留优势,或让失败者使用带抖动的退避。随后仍需验证新策略不会造成饥饿,并在所声明的调度或概率模型下重新陈述活性;“观察到一次成功”不足以证明无限执行不能复现振荡。

参考资料
  • Maurice Herlihy and Nir Shavit, The Art of Multiprocessor Programming, rev. 1st ed., Morgan Kaufmann, 2012,进展条件相关章节。
  • Nir Shavit and Dan Touitou, “Software Transactional Memory,” PODC 1995, pp. 204–213。
  • Gregory R. Andrews, Foundations of Multithreaded, Parallel, and Distributed Programming, Addison-Wesley, 2000。