diff --git a/notes/pics/deadlock-banker-multi.png b/notes/pics/deadlock-banker-multi.png index 95c57b6d..60ff2639 100644 Binary files a/notes/pics/deadlock-banker-multi.png and b/notes/pics/deadlock-banker-multi.png differ diff --git a/notes/计算机操作系统 - 死锁.md b/notes/计算机操作系统 - 死锁.md index f7c8ab44..a70bf8f9 100644 --- a/notes/计算机操作系统 - 死锁.md +++ b/notes/计算机操作系统 - 死锁.md @@ -20,6 +20,7 @@ * [程序中的死锁](#程序中的死锁) * [1. Java 线程死锁](#1-java-线程死锁) * [2. MySQL 死锁](#2-mysql-死锁) + * [附录:实测](#附录实测) * [参考资料](#参考资料) @@ -28,9 +29,9 @@

-- 互斥:每个资源要么已经分配给了一个进程,要么就是可用的。 -- 占有和等待:已经得到了某个资源的进程可以再请求新的资源。 -- 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放。 +- 互斥:每个资源要么已经分配给了一个进程,要么就是可用的; +- 占有和等待:已经得到了某个资源的进程可以再请求新的资源; +- 不可抢占:已经分配给一个进程的资源不能强制性地被抢占,它只能被占有它的进程显式地释放; - 环路等待:有两个或者两个以上的进程组成一条环路,该环路中的每个进程都在等待下一个进程所占有的资源。 ## 处理方法 @@ -50,7 +51,7 @@ 当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。 -大多数操作系统,包括 Unix,Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。 +大多数操作系统,包括 Unix、Linux 和 Windows,处理死锁问题的办法仅仅是忽略它。 ## 死锁检测与死锁恢复 @@ -72,10 +73,10 @@ 上图中,有三个进程三种资源,每个数据代表的含义如下: -- E 向量:资源总量 -- A 向量:资源剩余量 -- C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量 -- R 矩阵:每个进程请求的资源数量 +- E 向量:资源总量; +- A 向量:资源剩余量; +- C 矩阵:每个进程所拥有的资源数量,每一行都代表一个进程拥有资源的数量; +- R 矩阵:每个进程请求的资源数量。 进程 P1 和 P3 所请求的资源都得不到满足,只有进程 P2 可以,让 P2 执行,之后释放 P2 拥有的资源,此时 A = (3 1 2)。P1 可以执行,执行后释放 P1 拥有的资源,A = (4 2 2)。P3 也可以执行。所有进程都可以顺利执行,没有死锁。 @@ -83,8 +84,8 @@ 每个进程最开始时都不被标记,执行过程有可能被标记。当算法结束时,任何没有被标记的进程都是死锁进程。 -1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A。 -2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1。 +1. 寻找一个没有标记的进程 Pi,它所请求的资源小于等于 A; +2. 如果找到了这样一个进程,那么将 C 矩阵的第 i 行向量加到 A 中,标记该进程,并转回 1; 3. 如果没有这样一个进程,算法终止。 ### 3. 死锁恢复 @@ -143,8 +144,8 @@ 检查一个状态是否安全的算法如下: -- 查找 Need 矩阵中是否存在一行小于等于向量 Available。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的。 -- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 Available 中。 +- 查找 Need 矩阵中是否存在一行小于等于向量 Available。如果不存在这样的行,那么系统将会发生死锁,状态是不安全的; +- 假若找到这样一行,将该进程标记为终止,并将其已分配资源加到 Available 中; - 重复以上两步,直到所有进程都标记为终止,则状态是安全的。 如果一个状态不是安全的,需要拒绝进入这个状态。 @@ -193,7 +194,7 @@ synchronized (first) { } ``` -两个线程各反向转账 10 万次,原来的写法每次运行都会卡住,按 id 顺序加锁后都能完成,两个账户的余额仍为 1000。使用 ReentrantLock 时还可以用 tryLock 设置超时,拿不到锁就释放已持有的锁再重试,破坏的是占有和等待条件。 +两个线程各反向转账 10 万次,原来的写法每次运行都会卡住,按 id 顺序加锁后都能完成,两个账户的余额仍为 1000(JDK 21 实测,见[附录:实测](#附录实测))。使用 ReentrantLock 时还可以用 tryLock 设置超时,拿不到锁就释放已持有的锁再重试,破坏的是占有和等待条件。 ### 2. MySQL 死锁 @@ -212,7 +213,7 @@ UPDATE account SET balance = balance + 50 WHERE id = 1; -- ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction ``` -InnoDB 默认开启死锁检测,发现环后立即回滚其中一个事务(倾向于回滚修改行数少的事务),另一个事务继续执行。上例中事务 2 被回滚,事务 1 提交后余额为 900 和 1100。执行 SHOW ENGINE INNODB STATUS,在 LATEST DETECTED DEADLOCK 一节中可以看到两个事务各自持有和等待的锁。被回滚的事务需要由应用重试。 +InnoDB 默认开启死锁检测,发现环后立即回滚其中一个事务(倾向于回滚修改行数少的事务),另一个事务继续执行。上例中事务 2 被回滚,事务 1 提交后余额为 900 和 1100(MySQL 8.0 实测,见[附录:实测](#附录实测))。执行 SHOW ENGINE INNODB STATUS,在 LATEST DETECTED DEADLOCK 一节中可以看到两个事务各自持有和等待的锁。被回滚的事务需要由应用重试。 减少数据库死锁的方法: @@ -220,6 +221,37 @@ InnoDB 默认开启死锁检测,发现环后立即回滚其中一个事务( - 事务尽量短,不在事务中调用外部接口或等待用户操作; - 为更新的条件列建立索引,避免扫描时锁住过多的行。 +## 附录:实测 + +Java 线程死锁:JDK 21,两个线程分别执行 10 万次 transfer(a, b, 1) 和 transfer(b, a, 1),设为守护线程,各等待 3 秒后检查是否结束,各运行 3 次: + +```text +原写法:stuck +原写法:stuck +原写法:stuck +按 id 顺序加锁:1000 1000 +按 id 顺序加锁:1000 1000 +按 id 顺序加锁:1000 1000 +``` + +MySQL 死锁:MySQL 8.0.44,account 表中 id 为 1、2 的两行余额都是 1000,用两个连接按正文的顺序执行,最后执行 SHOW ENGINE INNODB STATUS(节选): + +```text +事务 1 的第二条 UPDATE: OK +事务 2 的第二条 UPDATE: OperationalError(1213, 'Deadlock found when trying to get lock; try restarting transaction') +提交后: ((1, 900), (2, 1100)) +LATEST DETECTED DEADLOCK +*** (1) TRANSACTION: +UPDATE account SET balance = balance + 100 WHERE id = 2 +*** (1) HOLDS THE LOCK(S): +RECORD LOCKS space id 53 page no 4 n bits 72 index PRIMARY of table `demo`.`account` trx id 3933 lock_mode X locks rec but not gap +*** (1) WAITING FOR THIS LOCK TO BE GRANTED: +RECORD LOCKS space id 53 page no 4 n bits 72 index PRIMARY of table `demo`.`account` trx id 3933 lock_mode X locks rec but not gap waiting +*** (2) TRANSACTION: +UPDATE account SET balance = balance + 50 WHERE id = 1 +*** WE ROLL BACK TRANSACTION (2) +``` + ## 参考资料 - Tanenbaum A S, Bos H. Modern Operating Systems [M]. Pearson, 2014.