多线程:死锁跟踪实用程序






4.91/5 (101投票s)
跟踪多线程应用程序中的死锁。
目录
以下是按相互关系分组的主题
引言
在多线程应用程序中,为了同步,通常遵循的编码格式是“lock”。多线程应用程序面临的一个主要问题是由于锁定语句实现不当而导致的“死锁”。要准确找出死锁发生在哪里,这是一项非常困难且繁琐的工作。尽管有一些第三方实用程序,或者您可以获取一个转储并分析死锁问题,但不幸的是,这并非易事,而且,还有可能在生产环境中出现死锁,导致 UI/活动冻结,而用户现场可能无法获取转储。
本文将解释如何获得死锁场景下的适当诊断信息,以便程序员可以快速有效地解决此类问题。
本文将带给您什么?
1. 识别线程中的死锁
可检测因“lock”实现不当而导致的死锁场景的内联代码/想法,并能提供有用的诊断信息来跟踪死锁。
2. 性能
此外,它还可以跟踪“lock”活动。如果多个线程尝试获取同一对象的锁,一次只有一个线程可以获取它,所有其他线程都将处于等待状态。此实用程序可以识别线程等待获取对象锁的时间以及活动状态时间(即,已获取锁并正在执行活动)。如果错误的对象被用于同步,从而损害了应用程序的性能,那么这种分析就很有用。
示例:假设代码包含四个方法,M1()
、M2()
、M3()
和 M4()
。这些方法中的每一个都锁定同一个对象,例如“obj
”。但为了同步,M1
和 M2
需要同步(它们共享一些公共数据),M3
和 M4
需要同步(M3 和 M4 之间共享的数据集不同)。但是,由于同一个对象用于“obj
”的同步,如果一个线程正在执行 M1()
,另一个线程尝试执行 M3()
,那么第二个线程将处于等待状态,直到 M1
中的锁被释放,但根据代码,M1
和 M3
不需要同步,因此在这种情况下,放置的锁是不正确的,它损害了应用程序的性能。
如何跟踪线程锁定活动?
这是一个通用的“lock
”语句,其中一个线程获取对象“obj
”的锁,一旦“lock
”的作用域结束,它就释放该对象的锁。
lock(obj)
{
// perform activity 1
// perform activity 2
// perform activity 3
}
这段代码类似于
Monitor.Enter(obj); //acquire lock on object
// perform activity 1
// perform activity 2
// perform activity 3
Monitor.Exit(obj); //release lock
使用 Monitor.Enter
和 Monitor.Exit
,我们获取并释放对象“obj
”的锁,但有一个问题。如果“activity 1/2/3”中发生异常且未处理,会发生什么?在这种情况下,Monitor.Exit
不会执行,导致“obj
”仅处于锁定状态,这是一个大问题。在“lock
”构造的情况下,当退出“lock
”时,会释放相应对象的锁。所以,一个解决方案是将 Monitor.Exit
放在 finally
中。但是,与 lock
构造相比,它不是用户友好的模式。所以,完美的解决方案是“using
”构造。当退出“using
”语句时,将调用 Dispose()
方法。因此,我们将 Monitor.Exit
放在 Dispose
方法中。我们可以有这样的代码
using(ThreadLock.Lock(obj))
{
// perform activity 1
// perform activity 2
// perform activity 3
}
这里,“ThreadLock
”是一个实现 IDisposable
接口的类,“Lock
”是一个静态方法,它返回 ThreadLock
的新实例。当退出“using
”语句时,将调用 Dispose()
方法,我们在其中移除对象的锁(Monitor.Exit()
)。
public static ThreadLock Lock(object objLock)
{
return new ThreadLock(objLock);
}
public ThreadLock(object objLock)
{
this.status = Status.Acquiring; //useful for detecting dead-lock
this.objLock = objLock;
//collect useful information about the context such
//as stacktrace, time to acquire the lock(T1)
Monitor.Enter(objLock);
this.status = Status.Acquired;
//lock is acuired, so collect acquired-time(T2)
//[T2-T1 = time taken to acquire lock]
}
public void Dispose()
{
Monitor.Exit(objLock);
//T3: activity in a lock is over
//Serialize this class for doing analysis of thread-lock activity time
}
关于示例
这是示例应用程序的主屏幕
1. 死锁部分
单击“Regular ‘lock’ test”按钮。它将对文本框中指定的迭代次数执行以下代码。在这种情况下,它是 100,000(十万)次。
//Execute following line 100000 (hundred thousand) times
lock (objLockTest)
{
}
单击“Using ‘ThreadLockTracer’ Utility”按钮。它将对文本框中指定的迭代次数执行以下代码。在这种情况下,它是 100,000(十万)次。
//Execute following line 100000 (hundred thousand) times
using (ThreadLock.Lock(objLockTest))
{
}
从结果中可以清楚地看出,“Thread Lock Tracer”实用工具效率很高;对于十万次迭代,它只多花了不到 100 毫秒的时间,这应该是可以接受的,但作为回报,在出现问题时,它提供了非常有用的独占诊断信息来解决问题。
- 单击“Generate Thread ‘Dead-Lock’”按钮。它会在线程之间生成死锁。
- 等待大约 3-5 秒,然后单击“Scan ‘Dead-Lock’”按钮。它会处理集合中存储的线程锁定信息,并根据该信息检测是否存在死锁。
- 如果找到死锁,它将显示如下详细信息
2. 跟踪线程活动部分
- 单击“Generate Thread Activity 1”和“Generate Thread Activity 2”以进行一些常规的线程锁定活动。5-8 秒后,单击“Scan Thread Activity”。它显示线程锁定活动详细信息,如下所示。
- 从该图中可以看出,浅色表示线程正在等待获取锁,深色(蓝色/红色)表示线程已获取锁达到的时间。
- 此外,单击任何水平图形线,它都会以蓝色高亮显示同一对象的记录。
注意
- 使用本文解释的概念,跟踪线程死锁功能非常高效(对于十万次迭代,大约只需 100 毫秒,此时间可能因您的机器而异),并且您可以将其集成到您的代码中。为此,我们只需要搜索并替换对“
lock
”语句的调用,因此工作量不应太大。 - 识别线程死锁可以在单独的线程中或在应用程序退出时进行。
- “跟踪线程活动”功能仅用于开发环境。跟踪这些信息在性能方面成本很高,因此建议在生产环境中将其关闭。
结论
- 跟踪因不当“
lock
”使用而导致的线程死锁非常简单易行,并使开发者的生活更加轻松。 - “跟踪线程活动”功能在开发环境中用于查找是否存在错误的锁,从而损害了多线程应用程序的性能。