20

5分钟前面试,3个问题没回答,谁能帮帮我。

问题:

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

我给的答案:

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

请帮助我理解这一点。

4

4 回答 4

12

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

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

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

例子

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

}
finally
{
    Monitor.Exit();
}

阅读更多

于 2013-03-12T13:14:32.117 回答
5

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

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

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

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

例如,在使用时,ReaderWriterLockSlim您可以使用超时来防止死锁(如果您等待太多,则中止获取锁)

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

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

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

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

于 2013-03-12T13:12:41.213 回答
4

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

就我个人而言,当我寻找死锁时,我首先会找到关键部分跨越函数调用的函数。例如

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

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

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

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

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

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

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

于 2013-03-12T13:09:47.683 回答
1

这个问题最简单的解决方案是总是休眠/等待足够大的超时。如果发生超时,您就知道某事花费的时间比它应有的时间长,并且您很有可能出现死锁或其他错误。

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

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

于 2013-03-12T13:03:31.190 回答