Deadlock
死锁:一组进程因竞争资源而互相等待对方释放资源,导致都无法继续执行。
四个必要条件
- 互斥Mutual exclusion:资源一次只能被一个进程占用
- 占有并等待Hold and wait:进程已占有部分资源,同时继续申请新资源
- 不可抢占No preemption:资源不能被强制夺走,只能由占有者主动释放
- 循环等待Circular wait:存在进程—资源的环形等待链
死锁处理的四类
A) 预防(Prevention)
破坏四个必要条件之一:
- 破坏占有并等待:一次申请全部资源 / 申请前先释放
- 破坏不可抢占:允许抢占并回滚
- 破坏循环等待:资源编号,按序申请
- 互斥通常难破坏
B) 避免(Avoidance)
分配资源前判断是否会进入不安全状态,只做“安全分配”。 典型:银行家算法(必考)。
安全状态:存在一个安全序列,使所有进程都能按序完成。 不安全 ≠ 一定死锁,但有死锁风险。
C) 检测与解除(Detection & Recovery)
允许死锁发生,周期性或按需检测:
- 单实例:等待图找环
- 多实例:检测算法(类似安全性检查)
D) 忽略(Ostrich)
鸵鸟策略.假设死锁很少发生,发生了由人工/重启解决
死锁产生的四个必要条件?
如何预防死锁?给一些常见策略。
什么是死锁避免?银行家算法的基本思想是?
死锁检测思路是什么?
在实际开发中,你遇到过死锁问题吗?如何排查和定位?
给你一段简单的伪代码(两个锁 A/B,两个线程互相反向加锁),请你指出是否会死锁,为什么?
锁竞争为什么会性能下降
串行化:并行直接变成排队
锁保护的临界区同一时刻只能一个线程执行。
- 没竞争:每个线程很快进出,成本主要是“加锁/解锁”那几条指令
- 有竞争:大量线程在同一把锁上排队,你的程序吞吐量上限就变成 ≈ 临界区执行时间的倒数
等待的成本
A. 自旋(spin)
线程在用户态循环检查锁是否释放。
- 优点:锁很快释放时更快(避免切换)
- 缺点:锁持有稍久就会烧 CPU,占用核心但不干活,还会影响别的线程运行。
B. 阻塞(sleep / futex / park)
线程把自己挂起,等锁释放再唤醒。
- 优点:不浪费 CPU
- 缺点:线程切换很贵:保存/恢复寄存器、调度器、可能的跨核唤醒等。
缓存
现代 CPU 每个核心都有自己的缓存。锁通常用一个共享内存位置表示(比如一个整数)。
当多个核心都在抢同一把锁时,会发生:
- 每次尝试加锁都要对那个内存位置做原子操作(如 CAS)
- 原子操作会触发缓存一致性协议(MESI 等)
- 锁所在的 cache line 会在各个核心之间反复“夺来夺去”(cache line bouncing)
结果:
- 总线/互连压力上升
- 每次原子操作延迟变大
- 性能会出现很陡的下降(线程越多越糟)
惊群效应
当一个线程释放锁时,如果有很多线程在等:
- 可能同时被唤醒
- 大家一起冲过来抢
- 只有一个成功,其余又睡回去/继续自旋
这会造成大量无效唤醒 + 无效竞争,吞吐下降、延迟抖动。