我在5分钟后接受采访,我没有回答3个问题,有人可以帮助我。
问题:
如何在多线程应用程序功能中查找死锁场景并防止它?
我给的答案:
我给出了死锁和锁,mutex,监视器,信号量的定义。他告诉我,这些是工具,但是如何寻找死锁场景,因为当我们盲目地使用这些工具时,它会花费他所说的性能:(
请帮我理解这一点。
答案 0 :(得分:6)
听起来你在解释死锁如何发生以及如何防止它们方面遇到了问题。
当两个(最少两个)线程中的每一个尝试获取已被另一个锁定的资源上的锁时,就会发生死锁。在资源1上锁定的线程1尝试获取资源2上的锁。同时,线程2在资源2上有一个锁并且它尝试获取资源1上的锁。两个线程永远不会放弃它们的锁,因此发生了DEADLOCK
避免死锁的最简单方法是使用超时值。 Monitor类(system.Threading.Monitor)可以在获取锁定期间设置超时。
实施例
try{
if(Monitor.TryEnter(this, 500))
{
// critical section
}
}
catch (Exception ex)
{
}
finally
{
Monitor.Exit();
}
答案 1 :(得分:5)
性能分析工具还可以帮助识别死锁等。这个问题将提供一些关于此主题的见解:C#/.NET analysis tool to find race conditions/deadlocks。
代码的可视化分析和锁的正确使用也很有用(您应该能够在检查代码时检测代码中的潜在问题),但对于复杂的应用程序来说可能非常困难。有时,只有在运行代码时才能看到死锁,而不仅仅是检查代码。
我不太了解你的面试官。有些人可能想知道您对锁定标准/指导性的了解程度,有些人可能想知道您是否知道如何使用工具,有些人可能想要这两者。例如,在我工作的公司,使用工具(特别是我们已经拥有和使用的工具)受到高度赞赏。但这并不意味着人们不应该具备防止编码死锁的技能。
仅仅为了锁定而锁定某些内容会影响性能,因为线程会相互等待。您必须分析工作流程以确定真正需要锁定的内容,何时使用何种类型的锁定(简单lock
或可能ReaderWriterLockSlim
)。
有许多典型的方法可以防止死锁。
例如,当使用ReaderWriterLockSlim
时,您可以使用超时来防止死锁(如果您等待很多,则中止获取锁定)
if (cacheLock.TryEnterWriteLock(timeout))
{
...
}
你应该能够建议这样的超时。
在这样的问题上,我预计至少会提到死锁的经典案例,比如严重使用嵌套锁(你应该知道你可以避免它们,为什么它们是坏的等等。)
主题非常大......你可以继续谈论这个问题。但没有定义。了解锁是什么以及知道在大规模多线程应用程序中使用锁/信号量/互斥锁是两回事。
答案 2 :(得分:4)
我认为面试问你一个棘手的问题。如果你可以使用静态分析来防止死锁......没有人会遇到死锁!
就个人而言,当我寻找死锁时,我首先找到临界区跨越函数调用的函数。例如
void func(){
lock(_lock){
func2();
}
}
func2
正在做什么并不是很清楚。也许它会在同一个线程上调度一个事件,这意味着事件仍然是关键部分的一部分。也许它会锁定不同的锁。也许它会调入线程池而不再重新进入,因为它现在位于不同的线程上!这些类型的地方是您可以开始看到发生死锁情况的地方:当您有多个非重入锁定位置时。
其他时候,当跟踪死锁场景时,我回溯并尝试找到所有线程的创建位置。我考虑了每个功能以及它实际可以运行的位置。如果您不确定,添加日志以记录函数调用的来源也可以提供帮助。
您还可以通过使用无锁数据结构来避免死锁(但这些结构需要尽可能多地使用)。您希望最小化对无锁结构的访问,因为每次访问它时都可以更改。
正如另一个答案中所提到的,你可以使用带有超时的互斥锁,但这并不能保证始终有效(如果你的代码需要比超时工作的时间更长?)。在另一条评论中提到,这可能是面试官所要求的。我发现在生产中这不是一个好主意。超时一直在变化,可能需要比预期更长的时间才能运行并超时。我认为最好让它死锁,进行进程转储,然后确切地找到持有锁的问题并解决问题。当然,如果您的业务需求不能允许,那么您可以将其作为防御性编码策略的一部分以及智能锁定位置选择。
我不同意你的访谈,锁始终添加了巨大的性能问题。无争用的锁/互斥锁/等在转移到操作系统和自旋锁之前首先作为自旋锁进行测试。
通常,避免死锁的最佳方法是了解您的程序流程。每次引入新的锁定对象时,都要考虑它的使用位置以及链条的使用方法。
答案 3 :(得分:0)
这个问题的最简单的解决方案是始终以足够大的超时睡眠/等待。如果超时发生,你知道某些事情比预期的要长,你很有可能出现死锁或其他错误。
Mutex m; // similar overloads exist for all locking primitives
if (!m.WaitOne(TimeSpan.FromSeconds(30)))
throw new Exception("Potential deadlock detected.");
当WaitOne
返回false
时,它将等待30秒并且锁定仍未释放。如果你知道所有锁定操作都应该在几毫秒内完成(如果没有,那么只是增加超时),那么这是一个非常好的迹象表明事情发生了很糟糕。