什么是 deadlock 死锁?

在编写多线程应用程序时,最常见的问题之一是死锁。

我的问题是:

  1. 什么是僵局?

  2. 你是怎么发现他们的?

  3. 你能搞定他们吗?

  4. 最后,如何防止它们的发生?

151903 次浏览

当多个进程试图同时访问相同的资源时,就会发生 锁定

一个进程失败了,必须等待另一个进程完成。

当等待进程仍然持有第一个资源完成之前需要的另一个资源时,就会发生 僵局

举个例子:

资源 A 和资源 B 由进程 X 和进程 Y 使用

  • X 开始使用 A。
  • X 和 Y 尝试开始使用 B
  • Y 赢了,第一个得到 B
  • 现在 Y 需要使用 A
  • A 被 X 锁定,等待 Y

避免死锁的最佳方法是避免以这种方式交叉进程。尽可能减少锁任何东西的需要。

在数据库中,避免在单个事务中对不同的表进行大量更改,避免触发器,并尽可能切换到乐观/脏/nolock 读取。

当两个线程获得阻止其中一个线程进行处理的锁时,就会发生死锁。避免它们的最佳方法是仔细开发。许多嵌入式系统通过使用看门狗定时器(一种定时器,只要系统挂起一段时间,它就会重置系统)来保护它们。

当线程在等待从未发生的事情时,就会发生死锁。

通常,当线程正在等待前一个所有者从未释放的互斥量或信号量时,就会发生这种情况。

这种情况也经常发生在涉及两个线程和两个锁的情况下,如下所示:

Thread 1               Thread 2


Lock1->Lock();         Lock2->Lock();
WaitForLock2();        WaitForLock1();   <-- Oops!

您通常会检测到它们,因为您期望发生的事情从来不会发生,或者应用程序完全挂起。

死锁只会发生在你有两个或两个以上的锁,可以在同一时间,他们是以不同的顺序抓取。

避免死锁的方法有:

  • 避免使用锁(如果可能的话) ,
  • 避免有多个锁
  • 总是按同样的顺序拿锁。

死锁是系统的一种状态,在这种状态下,没有一个进程/线程能够执行某个操作。正如其他人所提到的,死锁通常是这样一种情况的结果: 每个进程/线程都希望获得一个锁,锁指向已经被另一个(甚至相同的)进程/线程锁定的资源。

有很多方法可以找到它们并避免它们。一个人正在非常努力地思考和/或尝试很多事情。然而,处理并行性是出了名的困难,大多数(如果不是全部)人将无法完全避免问题。

如果您对处理这类问题是认真的,那么一些更正式的方法可能是有用的。我所知道的最实用的方法是使用过程理论的方法。在这里,您使用某种进程语言(例如 CCS、 CSP、 ACP、 mCRL2、 LOTOS)对系统建模,并使用可用的工具(model -)检查死锁(也许还有其他一些属性)。可以使用的工具集包括 FDR、 mCRL2、 CADP 和 Uppaal。一些勇敢的人甚至可能用纯粹的符号方法证明他们的系统没有死锁(定理证明; 查找 Owicki-Gries)。

然而,这些形式化的方法通常需要一些努力(例如,学习过程理论的基础知识)。但我想这只是这些问题困难的结果。

你可以看看这个 精彩的文章,在 僵局部分。它是在 C # 中,但其他平台的思想仍然是相同的。为方便阅读,我在此引用

当两个线程分别等待 另一个,所以两者都不能继续。最简单的方法来说明这一点 有两个锁:

object locker1 = new object();
object locker2 = new object();


new Thread (() => {
lock (locker1)
{
Thread.Sleep (1000);
lock (locker2);      // Deadlock
}
}).Start();
lock (locker2)
{
Thread.Sleep (1000);
lock (locker1);                          // Deadlock
}

互斥体实质上是一种锁,提供对共享资源的受保护访问。在 Linux 下,线程互斥数据类型是 pthread _ mutex _ t。在使用之前,初始化它。

要访问共享资源,必须锁定互斥对象。如果互斥锁已经锁定,则调用将阻塞线程,直到互斥锁解锁。访问共享资源完成后,您必须解锁它们。

总的来说,有一些不成文的基本原则:

  • 在使用共享资源之前获取锁。

  • 尽量缩短锁定时间。

  • 如果线程返回错误,则释放锁。

死锁发生在线程或进程的循环链中,每个线程或进程都持有一个锁定的资源,并试图锁定链中下一个元素所持有的资源。例如,两个线程分别持有锁 A 和锁 B,并且都试图获取另一个锁。

死锁是当不同进程请求的可用资源数量较少时发生的情况。这意味着当可用资源的数量少于用户请求的数量时,进程就处于等待状态。有时候等待增加的更多,没有任何机会检查资源不足的问题,然后这种情况被称为死锁。 实际上,死锁是我们面临的一个主要问题,它只发生在多任务操作系统中。死锁不能发生在单任务操作系统中,因为所有资源都只为当前正在运行的任务提供... ...

要定义死锁,首先要定义流程。

正如我们所知道的,进程只不过是执行中的一个 program

执行程序过程需要一些资源。资源类别可能包括内存、打印机、 CPU、打开的文件、磁带驱动器、 CD-ROM 等。

僵局 :死锁是指两个或两个以上的进程持有某些资源并试图获取更多资源时出现的一种情况或状况,它们只有在执行完成后才能释放资源。

僵局,僵局僵局的状况或处境

enter image description here

在上面的图中有两个进程 P1P2,并且有两个资源 R1R2

资源 R1被分配给进程 P1,资源 R2被分配给进程 P2。 要完成进程 P1的执行需要资源 R2,因此 P1请求 R2,但是 R2已经分配给 P2。译注:

同样,进程 P2完成其执行需要 R1,但是 R1已经分配给 P1

这两个进程都不能释放它们的资源,除非它们完成执行。因此,两者都在等待另一种资源,并将永远等待。这是 僵局条件。

为了发生死锁,必须满足四个条件。

  1. 互斥锁-每个资源当前只分配给一个进程,或者它是可用的。(两个进程不能 同时控制相同的资源或处于临界状态 部分)
  2. 坚持等待-当前持有资源的进程可以请求新资源。
  3. 没有优先权-一旦一个进程拥有一个资源,它就不能被另一个进程或内核带走。
  4. 循环等待-每个进程都在等待获得另一个进程所持有的资源。

所有这些条件都在上图中得到了满足。

死锁是操作系统中多处理/多编程问题中的一个常见问题。 假设有两个进程 P1、 P2和两个全局共享的资源 R1、 R2,在关键部分,两个资源都需要访问 最初,操作系统将 R1分配给进程 P1,R2分配给进程 P2。 由于两个进程同时运行,它们可能会开始执行代码,但当进程遇到关键部分时,就会出现问题。 所以进程 R1将等待进程 P2释放 R2,反之亦然。 因此,他们将永远等待(死锁条件)

一个小小的类比..。

你的母亲(OS) ,< br > 你(P1) ,< br > 你的兄弟(P2) ,< br > 苹果(R1) ,< br > 刀(R2) ,< br > 临界切片(用刀切苹果) 起初,你妈妈把苹果和刀子给了你哥哥 两个人都很开心并且在玩(执行他们的代码)。 < br > 你们中的任何人都想在某个时候切苹果(临界部分) 你不想把苹果给你的兄弟 你哥哥不想把刀给你 所以你们两个要等很长很长的时间

让我来解释一个现实世界(不实际)的例子,从犯罪电影僵局的情况。想象一下,一个罪犯挟持了一名人质,与之相对的是,一名警察也挟持了一名罪犯的朋友作为人质。在这种情况下,如果警察不让他的朋友放手,罪犯是不会放人质走的。除非罪犯释放人质,否则警察不会放走罪犯的朋友。这是一个永无止境的不可信赖的局面,因为双方都坚持从对方开始迈出第一步。

刑警现场

enter image description here

因此,当两个线程需要两个不同的资源,并且每个线程都拥有另一个线程所需的资源锁时,这就是死锁。

僵局的另一个高层次解释: 破碎的心

你正在和一个女孩约会,在一次争吵之后的一天,双方都伤心欲绝地等待着 对不起,我想你的电话。在这种情况下,当且仅当其中一方接到另一方的 对不起呼叫时,双方都希望彼此通信。因为双方都不会在被动的状态下开始交流或者等待,所以双方都会等待对方开始交流,最终导致交流陷入僵局。

上面的一些解释很不错,希望这个也有用: https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html

在数据库中,当一个会话(例如 ora)想要一个由另一个会话(例如 data)持有的资源,但是那个会话(data)也想要一个由第一个会话(ora)持有的资源时。也可以有两个以上的会议涉及,但想法将是相同的。 实际上,死锁阻止一些事务继续工作。 例如: 假设 ORA-DATA 持有锁 A 并请求锁 B SKU 持有锁 B 并请求锁 A

谢谢,

当一个线程正在等待其他线程完成时,就会发生死锁,反之亦然。

< p > 如何避免? < br > - 避免嵌套锁 < br > - 避免不必要的锁 - 使用线程连接()

< p > 你怎么检测出来的? < br > 在 cmd:

中运行这个命令
jcmd $PID Thread.print

怪胎

一个经典和非常简单的程序了解 僵局的情况:-

public class Lazy {


private static boolean initialized = false;


static {
Thread t = new Thread(new Runnable() {
public void run() {
initialized = true;
}
});


t.start();


try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}


public static void main(String[] args) {
System.out.println(initialized);
}
}
当主线程调用 Lazy.main 时,它检查类 Lazy 已初始化并开始初始化该类 主线程现在将初始化设置为 false,创建并启动一个背景 其 run 方法设置为 true 并等待后台线程完成的线程。

这一次,类正在由另一个线程初始化。 在这些情况下,当前线程,即后台线程, 等待 Class 对象,直到初始化完成 正在进行初始化的主线程正在等待后台 因为两个线程现在正在等待对方,所以 程序是 陷入僵局。

死锁不仅仅发生在锁上,尽管这是最常见的原因。在 C + + 中,可以通过在另一个线程的 std: : thread 对象上使用每个线程调用 join ()来创建具有两个线程且没有锁的死锁。

锁定并发控制

使用锁定来控制对共享资源的访问很容易出现死锁,而事务调度程序本身无法防止死锁的发生。

例如,关系数据库系统使用各种锁来保证交易的 ACID属性。

无论你使用什么关系数据库系统,当你修改某个表记录时(例如,UPDATE或者 DELETE) ,都会获得锁。如果不锁定由当前正在运行的事务修改的行,则 Atomicity将受到损害)。

什么是僵局

当两个并发事务无法进行处理时,就会发生死锁,因为每个事务都在等待另一个事务释放锁,如下图所示。

Deadlock

因为两个事务都处于锁获取阶段,所以都不会在获取下一个事务之前释放锁。

从僵局中恢复

如果您使用的是依赖于锁的并发控制算法,那么总是存在陷入死锁的风险。死锁可能发生在任何并发环境中,而不仅仅是在数据库系统中。

例如,如果两个或多个线程正在等待以前获取的锁,因此任何线程都无法取得任何进展,那么多线程程序就可能发生死锁。如果在 Java 应用程序中发生这种情况,JVM 不能仅仅强制 Thread 停止其执行并释放其锁。

即使 Thread类公开了一个 stop方法,该方法自 Java 1.1以来已被弃用,因为它可能导致对象在线程停止后处于不一致的状态。相反,Java 定义了一个 interrupt方法,它作为一个线程被中断的提示,可以简单地忽略中断并继续执行它。

由于这个原因,Java 应用程序无法从死锁情况中恢复,应用程序开发人员有责任以永远不会发生死锁的方式对锁获取请求进行排序。

但是,数据库系统不能强制执行给定的锁获取顺序,因为不可能预见某个事务想要进一步获取哪些其他锁。保持锁定顺序成为数据访问层的责任,而数据库只能帮助从死锁情况中恢复。

数据库引擎运行一个单独的进程,该进程扫描当前冲突图以查找锁等待周期(这是由死锁引起的)。 当检测到一个循环时,数据库引擎将选择一个事务并中止它,从而释放其锁,以便另一个事务能够取得进展。

与 JVM 不同,数据库事务被设计为一个原子工作单元。因此,回滚会使数据库处于一致的状态。

假设一个线程想同时从“ A 帐户 = > B 帐户”转账,另一个线程想同时从“ B 帐户 = > A 帐户”转账。这会导致 deadlock

第一个线程会锁定“帐户 A”,然后它必须锁定“帐户 B”,但它不能,因为第二个线程将已经锁定“帐户 B”。类似地,第二个线程不能锁定 A Account,因为它被第一个线程锁定。所以这个事务将仍然不完整,我们的系统将失去2个线程。为了防止这种情况,我们可以添加一个规则,按排序顺序锁定数据库记录。因此,线程应该查看帐户名或 ID,并决定锁定排序顺序: A = > B。这里将有一个竞争条件,无论谁赢,处理它的代码,第二个线程将接管。这是针对这种特定情况的解决方案,但死锁可能出现在许多原因中,因此每种情况都有不同的解决方案。

Os 有一个具有一定时间间隔的死锁检测机制,当它检测到死锁时,它会启动一个恢复方法。更多关于死锁侦测的资料

在这个例子中,我们丢失了2个线程,但是如果我们得到更多的死锁,这些死锁会导致系统崩溃。