Do Interfaces有助于避免.NET应用程序中的死锁



我试图了解我的应用程序是否因为I/O速度慢、计算成本高或对资源的互斥访问而挂起。在这种情况下,使用接口并在可能导致锁定的类中实现它们,可以避免锁定或不锁定。

我知道Interface是一种编程结构,我们在其中定义要公开给公共或其他模块的函数/服务,并避免循环依赖。

否。恰恰相反,由于接口隐藏了实现,您将更难看到导致问题的锁。

使用调试器的Debug+Windows+线程可以查看哪些线程正在运行,哪些线程正在执行获取锁的代码。您还可以看到拥有锁的线程的线程ID,请检查此答案。

不,接口与死锁完全无关。如果您有死锁,那么根据定义,这是因为对共享资源的同步访问不会减慢I/O):

死锁是指两个或多个相互竞争的操作都在等待另一个操作完成,因此两者都没有完成。

如何调试它们
死锁的原因有很多(当然,某些情况——I/O速度慢、CPU密集型算法——可能会导致死锁更频繁地出现,但它们不是原因)。

大多数情况下,如果可以将调试器附加到应用程序,就可以很容易地检测到它们。挂起时暂停执行,浏览线程列表和它们的堆栈,您可以看到它们是否在等待共享资源。

一个非常常见的死锁场景是:

  • 线程1获取对资源A的锁定
  • 线程2获取对资源B的锁定
  • 线程1试图获取对资源B的锁定,但它被线程2锁定,然后线程1将被挂起(稍微简化一点)
  • 线程2试图获取对资源A的锁定,但它被线程1锁定,然后线程2将被挂起
  • 两个线程都被挂起,然后它们就无法释放所获取的锁。如果没有超时,则此条件将不会得到解决(应用程序或其一部分可能会挂起)

另一种非常常见的情况:

  • 线程1获取资源a上的锁(例如,不使用C#中的lock语句,而是直接使用Monitor.Enter()或通过另一个同步原语(如ReadWriterLockSlim))
  • 由于出现异常,执行失败。已处理异常,但未释放锁
  • 第一次,另一个线程(或者如果锁支持重入,则是同一个线程)尝试使用同一个锁时,应用程序将挂起(因为从未释放过锁)

这是一个代码示例(我知道,非常天真的锁定机制):

object _syncRoot = new object();
void ThreadWorker(object state ) {
Monitor.Enter(_syncRoot);
try {
// Do some operation that may fail
// Release lock
Monitor.Exit(_syncRoot);
}
catch (Exception e) {
// Log error
}
}

幸运的是,这很容易避免(检查这个答案,看看锁是如何实现监视器的)。

try {
lock (_syncRoot) {
// Do your job here   
}
}

在前面的场景中,如果同一个线程重新访问这个锁,它不会挂起(但其他线程会挂起),但如果您使用读/写锁,它就会挂起(或者,如果它不支持可重入,它会失败,请参阅此问题以了解其他详细信息)。如果您对锁使用超时,则可能会检测到这种故障(当然,这并不总是可能的)。当锁定尝试超时时,应用程序将检测到它并采取适当的操作(如果有的话,甚至可能只是优雅地中止)。

接口不会更改运行时执行的代码。锁定纯粹是运行时的事情。对于死锁,只有实际执行的代码才重要。如何调用方法并不重要。

最新更新