当多个进程试图同时访问同一个资源时,就会发生锁。
一个进程失败,必须等待另一个进程完成。
当等待进程在完成之前仍然持有第一个需要的另一个资源时,就会发生死锁。
所以,举个例子:
资源 A 和资源 B 被进程 X 和进程 Y 使用
开始使用 A。
X 和 Y 尝试开始使用 B
Y“获胜”并首先获得 B
现在 Y 需要使用 A
A被X锁定,X正在等待Y
避免死锁的最好方法是避免进程以这种方式交叉。尽可能减少锁定任何东西的需要。
在数据库中,避免在单个事务中对不同的表进行大量更改,避免触发器并尽可能切换到乐观/脏/无锁读取。
让我解释一个真实世界(实际上不是真实的)例子,说明犯罪电影中的僵局情况。想象一个罪犯劫持了人质,而警察也劫持了一个人质,而人质是罪犯的朋友。在这种情况下,如果警察不让他的朋友放手,罪犯就不会放人质。除非罪犯释放人质,否则警察不会放过罪犯的朋友。这是一个没完没了的不信任局面,因为双方都在坚持从对方迈出第一步。
犯罪和警察场景
https://i.stack.imgur.com/3XVzK.png
简单来说,当两个线程需要两个不同的资源,并且每个线程都拥有另一个需要的资源的锁时,这就是死锁。
死锁的另一个高级解释:心碎
您正在与一个女孩约会,并且在吵架后的一天,双方都心碎并等待着一个对不起,我想念你的电话。在这种情况下,当且仅当其中一方收到另一方的“对不起”电话时,双方都希望相互交流。因为双方都不会开始通信并处于被动状态等待,所以双方都会等待对方开始通信,最终陷入死锁情况。
只有当您有两个或多个可以同时获取的锁并且以不同的顺序获取它们时,才会发生死锁。
避免死锁的方法是:
避免使用锁(如果可能),
避免拥有多个锁
总是按相同的顺序拿锁。
要定义死锁,首先我要定义进程。
流程 :我们知道流程只不过是一个正在执行的program
。
资源:执行程序过程需要一些资源。资源类别可能包括内存、打印机、CPU、打开的文件、磁带驱动器、CD-ROM 等。
死锁:死锁是两个或多个进程正在持有一些资源并试图获取更多资源的情况或条件,并且它们在完成执行之前无法释放资源。
死锁条件或情况
https://i.stack.imgur.com/DDuIL.png
在上图中,有两个进程 P1 和 p2,并且有两个资源 R1 和 R2。
资源 R1 分配给进程 P1,资源 R2 分配给进程 p2。为了完成进程 P1 的执行需要资源 R2,所以 P1 请求 R2,但 R2 已经分配给 P2。
同样,进程 P2 完成其执行需要 R1,但 R1 已经分配给 P1。
两个进程都不能释放它们的资源,除非它们完成它们的执行。所以两者都在等待另一个资源,他们将永远等待。所以这是一个死锁条件。
为了发生死锁,必须满足四个条件。
互斥——每个资源要么当前分配给一个进程,要么可用。 (两个进程不能同时控制同一个资源或处于它们的临界区)。 Hold and Wait - 当前持有资源的进程可以请求新资源。无抢占 - 一旦一个进程拥有资源,它就不能被另一个进程或内核夺走。循环等待 - 每个进程都在等待获取由另一个进程持有的资源。
并且所有这些条件都在上图中得到满足。
当线程正在等待从未发生过的事情时,就会发生死锁。
通常,当线程正在等待前一个所有者从未释放的互斥锁或信号量时,就会发生这种情况。
当您遇到涉及两个线程和两个锁的情况时,它也经常发生,如下所示:
Thread 1 Thread 2
Lock1->Lock(); Lock2->Lock();
WaitForLock2(); WaitForLock1(); <-- Oops!
您通常会检测到它们,因为您期望发生的事情永远不会发生,或者应用程序完全挂起。
您可以在 Deadlock 部分查看此wonderful articles。它在 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
}
死锁是操作系统中多处理/多道程序问题中的常见问题。假设有两个进程 P1、P2 和两个全局可共享资源 R1、R2,并且在关键部分需要访问这两个资源
最初,操作系统将 R1 分配给进程 P1,将 R2 分配给进程 P2。由于两个进程同时运行,它们可能会开始执行它们的代码,但是当进程到达临界区时就会出现问题。所以进程 R1 将等待进程 P2 释放 R2,反之亦然......所以他们将永远等待(死锁条件)。
一个小类比...
你的母亲(OS),你(P1),你的兄弟(P2),苹果(R1),刀(R2),临界区(用刀切苹果)。你妈妈一开始就把苹果和刀给了你哥哥。两人都很开心并且在玩(执行他们的代码)。你们中的任何人都想在某个时候切苹果(关键部分)。你不想把苹果给你哥哥。你哥哥不想把刀给你。所以你们俩都要等很久很久:)
当两个线程获得阻止其中任何一个进程的锁时,就会发生死锁。避免它们的最好方法是仔细开发。许多嵌入式系统通过使用看门狗计时器(如果系统挂起一段时间,则重置系统的计时器)来防止它们。
当存在一个循环的线程或进程链,每个线程或进程都持有一个锁定的资源并试图锁定链中下一个元素持有的资源时,就会发生死锁。例如,两个线程分别持有锁 A 和锁 B,并且都试图获取另一个锁。
基于锁的并发控制
使用锁定来控制对共享资源的访问很容易出现死锁,并且仅靠事务调度程序无法阻止它们的发生。
例如,关系数据库系统使用各种锁来保证事务 ACID
的属性。
无论您使用什么关系数据库系统,在修改(例如,UPDATE
或 DELETE
)某个表记录时总是会获取锁。如果不锁定由当前正在运行的事务修改的行,Atomicity
将受到损害)。
什么是死锁
当两个并发事务无法进行时,就会发生死锁,因为每个事务都在等待另一个释放锁,如下图所示。
https://i.stack.imgur.com/Glwqs.png
因为两个事务都处于锁获取阶段,所以没有一个事务在获取下一个之前释放锁。
从死锁情况中恢复
如果您使用依赖于锁的并发控制算法,那么总是存在陷入死锁情况的风险。死锁可能发生在任何并发环境中,而不仅仅是在数据库系统中。
例如,如果两个或多个线程正在等待先前获得的锁,则多线程程序可能会死锁,因此没有线程可以取得任何进展。如果这发生在 Java 应用程序中,JVM 不能仅仅强制线程停止执行并释放其锁。
即使 Thread
类公开了 stop
方法,该方法自 Java 1.1 以来已被弃用,因为它可能导致对象在线程停止后处于不一致状态。取而代之的是,Java 定义了一个 interrupt
方法,它作为一个提示,因为被中断的线程可以简单地忽略中断并继续执行。
出于这个原因,Java 应用程序无法从死锁情况中恢复,应用程序开发人员有责任以不会发生死锁的方式对锁获取请求进行排序。
但是,数据库系统无法强制执行给定的锁获取顺序,因为无法预见某个事务将进一步获取哪些其他锁。保持锁定顺序成为数据访问层的职责,而数据库只能协助从死锁情况中恢复。
数据库引擎运行一个单独的进程,该进程扫描当前冲突图的锁定等待周期(由死锁引起)。当检测到一个循环时,数据库引擎选择一个事务并中止它,导致其锁被释放,以便另一个事务可以进行。
与 JVM 不同,数据库事务被设计为一个原子工作单元。因此,回滚使数据库处于一致状态。
用于理解死锁情况的经典且非常简单的程序:-
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 类是否已经初始化并开始初始化该类。主线程现在将 initialized 设置为 false ,创建并启动一个后台线程,其 run 方法将 initialized 设置为 true ,并等待后台线程完成。
这一次,该类当前正在由另一个线程初始化。在这些情况下,当前线程(即后台线程)会等待 Class 对象,直到初始化完成。不幸的是,正在执行初始化的线程,即主线程,正在等待后台线程完成。因为两个线程现在正在互相等待,所以程序处于死锁状态。
死锁是系统的一种状态,其中没有单个进程/线程能够执行操作。正如其他人所提到的,死锁通常是每个进程/线程希望获得对已经被另一个(甚至相同)进程/线程锁定的资源的锁的结果。
有多种方法可以找到它们并避免它们。一种是非常努力地思考和/或尝试很多事情。然而,处理并行性是出了名的困难,大多数(如果不是全部)人将无法完全避免问题。
如果您认真处理这类问题,一些更正式的方法可能会很有用。我知道的最实用的方法是使用过程理论方法。在这里,您可以使用某种过程语言(例如 CCS、CSP、ACP、mCRL2、LOTOS)对系统建模,并使用可用工具(建模)检查死锁(可能还有其他一些属性)。使用的工具集示例有 FDR、mCRL2、CADP 和 Uppaal。一些勇敢的人甚至可能通过使用纯符号方法(定理证明;寻找 Owicki-Gries)来证明他们的系统没有死锁。
然而,这些形式化方法通常确实需要一些努力(例如学习过程理论的基础知识)。但我想这只是这些问题很难解决的结果。
死锁是当不同进程请求的可用资源数量较少时发生的情况。这意味着当可用资源的数量少于用户请求的数量时,此时进程进入等待状态。有时等待增加更多,没有机会检查资源不足的问题这种情况称为死锁。实际上,死锁对我们来说是一个主要问题,它只发生在多任务操作系统中。死锁不会发生在单任务操作系统中,因为所有资源都只存在于当前正在运行的那个任务......
上面的一些解释很好。希望这也有用:https://ora-data.blogspot.in/2017/04/deadlock-in-oracle.html
在数据库中,当一个会话(例如 ora)想要另一个会话(例如数据)持有的资源时,但该会话(数据)也想要由第一个会话(ora)持有的资源。也可能涉及 2 个以上的会话,但想法是相同的。实际上,死锁会阻止某些事务继续工作。例如:假设,ORA-DATA 持有锁 A 并请求锁 B 并且 SKU 持有锁 B 并请求锁 A。
谢谢,
当一个线程正在等待其他线程完成时会发生死锁,反之亦然。
如何避免? - 避免嵌套锁 - 避免不必要的锁 - 使用线程 join()
你如何检测它?在 cmd 中运行此命令:
jcmd $PID Thread.print
reference:geeksforgeeks
死锁不仅仅发生在锁上,尽管这是最常见的原因。在 C++ 中,您可以通过让每个线程在 std::thread 对象上为另一个线程调用 join() 来创建具有两个线程且没有锁的死锁。
互斥锁本质上是一个锁,提供对共享资源的受保护访问。在 Linux 下,线程互斥数据类型是 pthread_mutex_t。使用前,先对其进行初始化。
要访问共享资源,您必须锁定互斥锁。如果互斥锁已经上锁,则调用将阻塞线程,直到互斥锁解锁。完成对共享资源的访问后,您必须解锁它们。
总的来说,有一些不成文的基本原则:
在使用共享资源之前获取锁。
保持锁的时间尽可能短。
如果线程返回错误,则释放锁。