Skip to content

Deadlock

死锁:一组进程因竞争资源而互相等待对方释放资源,导致都无法继续执行。

四个必要条件

  1. 互斥Mutual exclusion:资源一次只能被一个进程占用
  2. 占有并等待Hold and wait:进程已占有部分资源,同时继续申请新资源
  3. 不可抢占No preemption:资源不能被强制夺走,只能由占有者主动释放
  4. 循环等待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)

结果:

  • 总线/互连压力上升
  • 每次原子操作延迟变大
  • 性能会出现很陡的下降(线程越多越糟)

惊群效应

当一个线程释放锁时,如果有很多线程在等:

  • 可能同时被唤醒
  • 大家一起冲过来抢
  • 只有一个成功,其余又睡回去/继续自旋

这会造成大量无效唤醒 + 无效竞争,吞吐下降、延迟抖动。