如果我在Thread.Sleep(0)
方法中评论或传递0,那么就没有死锁。在其他情况下,存在僵局。 uptask
由线程轮询中的线程执行,这需要一些时间。同时,主线程获取lockB,lockA并打印字符串并释放锁。之后uptask
开始运行,它看到lockA和lockB是空闲的。所以在这种情况下没有死锁。但是,如果我在平均时间uptask
前进入主线程并且看到lockB被锁定并且发生了死锁。任何人都可以更好地解释或验证这是否是原因?
class MyAppClass
{
public static void Main()
{
object lockA = new object();
object lockB = new object();
var uptask = Task.Run(() =>
{
lock (lockA)
{
lock (lockB)
{
Console.WriteLine("A outer and B inner");
}
}
});
lock (lockB)
{
//Uncomment the following statement or sleep with 0 ms and see that there is no deadlock.
//But sleep with 1 or more lead to deadlock. Reason?
Thread.Sleep(1);
lock (lockA)
{
Console.WriteLine("B outer and A inner");
}
}
uptask.Wait();
Console.ReadKey();
}
}
答案 0 :(得分:5)
你真的不能依靠Thread.Sleep
来防止僵局。它在您的环境中工作了一段时间。它可能不会一直有效,可能无法在其他环境中运行。
由于您以相反的顺序获取锁,因此存在死锁的可能性。
要防止死锁,请确保按顺序获取锁(例如,锁定A然后在两个线程中使用lockB)。
我最好的猜测是,如果你没有睡觉,那么主线程将在另一个线程(来自线程池)获得lockA之前获取并释放两个锁。请注意,在线程池上安排和运行Task需要一些时间。在大多数情况下,它是可以忽略的。但就你的情况而言,它有所作为。
要验证这一点,请在uptask.Wait()
之前添加以下行:
Console.WriteLine("main thread is done");
从线程池线程获取lockA之后的这一行:
Console.WriteLine("Thread pool thread: obtained lockA");
如果没有死锁,在线程池线程将其消息打印到控制台之前,您将看到第一条消息打印到控制台。
答案 1 :(得分:3)
你有两个主题。主线程和执行任务的线程。如果主线程能够获取lockB
并且任务线程能够获取lockA
,那么您就会遇到死锁。你永远不应该以不同的顺序锁定两个不同的资源,因为这会导致死锁(但我希望你已经根据问题的抽象性质知道了这一点)。
在大多数情况下,任务线程会稍微延迟,然后主线程将获得两个锁,但如果在Thread.Sleep(1)
和lockA
之间插入lockB
,则任务线程能够在主线程和BAM之前得到lockA
!你有一个僵局。
但是,Thread.Sleep(1)
不是获得死锁的必要条件。如果操作系统决定以某种方式调度任务线程,使其能够在主线程之前获得lockA
,那么就会遇到死锁,并且因为您的快速机器上没有死锁,您可能会遇到其他问题的死锁处理资源较少的计算机。
以下是一个说明,可以直观地解释为什么延迟会增加陷入僵局的可能性:
答案 2 :(得分:1)
来自https://msdn.microsoft.com/en-us/library/d00bd51t(v=vs.110).aspx,“millisecondsTimeout”
输入:System.Int32
milliseconds
暂停的thread
个数。 如果millisecondsTimeout参数的值为零,则线程将其时间片的剩余部分放弃到准备运行的任何具有相同优先级的线程。如果没有其他具有相同优先级的线程准备好运行,则不会暂停执行当前线程。“
答案 3 :(得分:0)
Martin Liversage简明扼要地回答了这个问题。
要解释一下,即使没有Thread.Sleep()语句,实验中的代码也容易出现死锁。如果没有Thread.Sleep()语句,发生死锁的概率窗口非常小,可能需要很长时间才能发生。这是您在省略Thread.Sleep()语句时没有遇到的原因。通过在第19行添加任何耗时的逻辑(即:Thread.Sleep),可以展开此窗口并增加死锁的可能性。
此外,此窗口可以通过在不同的硬件/操作系统上运行代码来扩展/减少,其中任务调度可能不同。