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