【问题标题】:How to find out deadlock and prevent it in C# [closed]如何在 C# 中找出死锁并防止它[关闭]
【发布时间】:2013-03-12 12:54:24
【问题描述】:

我在 5 分钟前接受了面试,我没有回答 3 个问题,有人可以帮帮我吗。

问题:

如何在多线程应用函数中寻找死锁场景并防止它?

我给出的答案:

我给出了死锁和锁、互斥体、监视器、信号量的定义。他告诉我,这些都是工具,但是如何寻找死锁的场景,因为当我们盲目使用这些工具时,它会牺牲性能他说:(

请帮助我理解这一点。

【问题讨论】:

  • 您必须分析锁定模式...想不出一个非常紧凑的答案。
  • 他要求一种通用的方法来检查死锁?我所知道的只是查看代码并使用一些智能...
  • 为了防止死锁,你必须使用无锁实现。如果您绝对需要保留锁,则必须确保在需要多个锁时,您锁定的对象以明确定义的顺序锁定。
  • 谢谢大家,我真的不知道如何实现这些东西,我是新手:(这些对我来说看起来很可怕,但是我应该通过一些视频来理解它:(
  • @TheKingDave:谢谢,我是新手 :( 他问的太多了,就像 Henk 说的那样,不能简洁地回答 :( 老实说,当你们自己说这不是很容易但可行,他怎么会问我这么大的问题:(啊!!我会研究一些资源来理解多线程的概念。再次感谢你:) 干杯

标签: c# asp.net .net c#-4.0 deadlock


【解决方案1】:

听起来您在解释死锁如何发生以及如何防止死锁时遇到了问题。

当每个线程(至少两个)尝试获取已被另一个锁定的资源上的锁时,就会发生死锁。锁定在资源 1 上的线程 1 尝试获取资源 2 上的锁。同时,线程 2 拥有资源 2 上的锁并尝试获取资源 1 上的锁。两个线程从不放弃它们的锁,因此出现死锁发生。

避免死锁的最简单方法是使用超时值。 Monitor 类(system.Threading.Monitor)可以在获取锁时设置超时时间。

例子

try{
    if(Monitor.TryEnter(this, 500))
    {
        // critical section
    }
}
catch (Exception ex)
{

}
finally
{
    Monitor.Exit();
}

Read More

【讨论】:

    【解决方案2】:

    性能分析工具也有助于识别死锁等。这个问题将提供对该主题的一些见解:C#/.NET analysis tool to find race conditions/deadlocks。

    代码的可视化分析和锁的正确使用也很有用(您应该能够在检查代码时检测到代码中的潜在问题),但对于复杂的应用程序可能非常困难。有时死锁只有在运行代码时才可见,而不仅仅是通过检查代码。

    我不太了解你的面试官。有些人可能想看看你对锁定标准/指南了解多少,有些人可能想看看你是否知道如何使用你的工具,有些人可能两者都想要。例如,在我工作的公司中,工具(尤其是我们已经拥有和使用的工具)的使用受到高度赞赏。但这并不意味着一开始就不应该具备防止编码死锁的技能。

    仅仅为了锁定而锁定某些东西会影响性能,因为线程会相互等待。您必须分析工作流程以确定真正需要锁定的内容、时间、锁定类型(简单的lock 或ReaderWriterLockSlim)。 防止死锁的典型方法有很多。

    例如,当使用ReaderWriterLockSlim 时,您可以使用超时来防止死锁(如果等待时间过长,则中止获取锁)

    if (cacheLock.TryEnterWriteLock(timeout))
    {
    ...
    }
    

    你应该能够建议这样的超时。

    在这样的问题上,我希望至少能提到典型的死锁案例,比如嵌套锁的使用不当(你应该能够知道你能避免它们,它们为什么不好,等等)。

    这个话题很大...你可以继续讨论这个。但没有定义。知道什么是锁和知道在大规模多线程应用程序中使用锁/信号量/互斥锁是两件不同的事情。

    【讨论】:

      【解决方案3】:

      我认为面试是在问你一个棘手的问题。如果您可以使用静态分析来防止死锁...没有人会死锁!

      就我个人而言,当我查找死锁时,我会首先查找临界区跨越的函数超过函数调用。例如

      void func(){
          lock(_lock){
              func2();
           }
      }
      

      目前还不清楚func2 在做什么。也许它在同一个线程上分派一个事件,这意味着该事件仍然是关键部分的一部分。也许它会锁定一个不同的锁。也许它分派到线程池中并且不再是可重入的,因为它现在在不同的线程上!这些地方是您可以开始看到死锁场景发生的地方:当您有多个不可重入锁位置时。

      其他时候,在跟踪死锁场景时,我会回溯并尝试找出所有线程在哪里创建。我考虑了每个功能以及它可以实际运行的位置。如果您不确定,添加日志以记录函数调用的来源也会有所帮助。

      您还可以通过使用无锁数据结构来避免死锁(但这些数据结构需要同样多的时间才能使用)。您希望尽量减少对无锁结构的访问,因为每次访问它都会发生变化。

      正如另一个答案中提到的,您可以使用带超时的互斥锁,但这不能保证始终有效(如果您的代码需要比超时工作更长时间怎么办?)。在另一条评论中提到,这可能是面试官要求的。我发现在生产中这并不是一个好主意。超时一直在变化,也许某些东西运行的时间比预期的要长并超时。我认为最好让它死锁,进行进程转储,然后准确找到持有锁的内容并解决问题。当然,如果您的业务需求不允许这样做,那么您可以将其用作防御性编码策略的一部分以及智能锁放置选择。

      我不同意您的采访,即锁定总是会增加巨大的性能问题。无争用锁/互斥锁/等在移交给操作系统之前首先作为自旋锁进行测试,自旋锁很便宜。

      一般来说,避免死锁的最佳方法是了解您的程序流程。每次您引入一个新的锁对象时,请考虑它在哪里使用以及在链中使用它的对象。

      【讨论】:

      • 感谢 devshorts 的解释,它为我提供了大量信息 :) 干杯
      【解决方案4】:

      解决此问题的最简单方法是始终休眠/等待足够长的超时时间。如果发生超时,您就知道某事花费的时间超出了应有的时间,并且您很可能会出现死锁或其他错误。

      Mutex m; // similar overloads exist for all locking primitives
      if (!m.WaitOne(TimeSpan.FromSeconds(30)))
          throw new Exception("Potential deadlock detected.");
      

      当WaitOne 返回false 时,它将等待 30 秒,而锁仍然不会被释放。如果您知道所有锁定的操作都应该在几毫秒内完成(如果没有,那么只需增加超时时间),那么这是一个非常好的迹象,表明某些事情出了问题。

      【讨论】:

      • 它如何区分死锁和饥饿?
      • 嗨 Hnagis,谢谢你的回答,恐怕我没听懂你在说什么 :( 我是多线程新手 :( 对不起,如果你能改写一下就好了你想说:(
      • 他正在对互斥体设置超时。这意味着它将等待最多 30 秒,然后再继续。如果超时,该函数将返回 false,因此他可以知道没有人对互斥锁进行脉冲处理。如果超时时间足够长,这可能意味着死锁。可能是这里的关键字。这并不总是意味着僵局。
      • 有必要区分吗?饥饿并不适用于所有场景,当它出现时,您可能也想将其视为一个错误。这不是一个理想的场景,但如果可以为每个 .NET 锁定机制发明一个,那么它就会被内置默认。
      • 我认为@Knaģis 有一个巧妙的解决方案,但我不认为它适用于所有地方。您需要了解您的程序流程和预期延迟才能做出这种假设。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多