用途
死锁预防用于解决多进程或多线程因互相等待对方持有的资源而永久卡死的问题。死锁不会崩溃、不报错,只是静默地永久冻结。当代码需要同时获取多个锁或资源、存在跨表或跨服务的嵌套事务、分布式系统进行跨服务调用,或出现“系统在高负载下冻结”“无错误但操作永不完成”等现象时,就应使用本技能。
核心原则
资料给出的铁律是:绝不在代码库中以不一致的顺序获取锁。没有例外——不能因为“这条路径很少走”“我们会加超时”“不同服务不会冲突”“只在重负载下发生”而放松。不一致的锁顺序必然导致最终死锁。
死锁需要同时满足四个条件,打破任意一个即可预防:
- 互斥:资源只能被一个持有者占用
- 持有并等待:持有一个资源的同时等待另一个
- 不可抢占:无法强制释放资源
- 循环等待:A 等 B,B 等 A
最实用的做法是通过一致排序打破循环等待。
主要能力与预防策略
- 一致锁排序(主要策略):始终按相同顺序获取锁,例如按账户 ID 从小到大加锁,释放时按相反顺序。
- 锁超时(打破不可抢占):为获取锁设置超时,超时后返回失败而非永久等待,并配合抖动退避。
- 带退避的 try-lock(打破持有并等待):不持有锁等待,获取失败则释放已持有的全部锁并重试,采用指数退避加抖动。
- 对相关资源使用单一锁:用一个更高层级的锁覆盖所有相关资源,牺牲并行度换取无死锁风险,适合操作快、正确性优先于吞吐量的场景。
- 锁层级:定义层级(如系统、区域、账户、事务),始终先获取父级再获取子级,违反层级则抛出异常。
数据库与分布式模式
SQL 中应始终按 ID 排序后加锁,例如使用 ORDER BY id FOR UPDATE,避免不同事务以相反顺序锁定同一批行。PostgreSQL 可设置 SET lock_timeout = '5s',超时后事务失败而非永久挂起。分布式系统可使用带租约的分布式锁,通过 Redis 的原子 set-if-not-exists 加 TTL 实现,释放时用 Lua 脚本校验租约 ID 后再删除。
使用方式
资料提供了运行时死锁检测思路:维护等待图(谁在等谁),记录等待关系时检查是否形成环,若成环则抛出死锁检测错误。此外还给出压力抵抗协议,用于回应“加长超时就行”“只在重负载下发生”“不同服务不会冲突”“重启即可”等常见借口,强调超时只是安全网而非解决方案,应修复锁顺序。
适用场景
适用于需要多锁获取、嵌套事务、跨服务调用、分布式锁协调的并发系统,尤其是金融转账、库存扣减等共享资源竞争场景。
限制与注意事项
资料未提供具体编程语言、框架或运行环境的版本要求,也未说明性能开销或兼容性限制。文档强调锁超时不能消除死锁,只是缩短失败前的等待;重启会杀死进行中的事务并带来数据损坏风险,不能作为解决方案。危险信号包括:不同代码路径以不同顺序加锁、无清晰层级的嵌套加锁、端点偶尔挂起、无锁超时、跨异步操作持锁、跨服务加锁无协调、在循环中加锁。出现任一信号都应重新设计锁策略。检查表要求:所有加锁遵循一致顺序、配置锁超时、在 finally 块中释放锁、尽量避免跨 await 边界持锁、部署死锁检测与监控、为跨团队代码记录锁顺序。