随着多核处理器变得越来越常见,软件开发人员就需要构建多线程应用程序 ,从而利用更多的处理能力来实现更高的性能。借助并行线程的强大功能,您可 以将整个工作划分为多项单独的任务,并以并行模式执行这些任务。
但是,线程经常需要相互通信才能完成任务;而且根据算法或数据访问的要 求,有时还需要同步线程的行为。例如,应该以互斥的方式授予线程对同一数据 的同时写访问权限,以避免数据损坏。
同步操作通常是通过使用共享的同步对象来完成的。对于获得该对象的线程 ,将授予其对敏感代码或数据的共享或独占访问权限。当不再需要访问权限时, 该线程将放弃所有权,而其他线程就可以尝试获取访问权限。根据所使用的同步 类型,同时请求所有权可能会使多个线程能够同时访问共享资源,也可能会阻止 某些线程,直到共享对象从上一次获取中释放为止。具体的示例包括:C/C++ 中 使用 EnterCriticalSection 和 LeaveCriticalSection 访问例程的关键部分, C/C++ 中的 WaitForSingleObject 函数,以及 C# 中的锁定语句和 Monitor 类 。
选择同步机制时必须谨慎,因为不恰当的线程同步不但不能提高性能,反而 会降低性能,这与多线程的目标背道而驰。因此,能否检测由于锁争用没有进展 而导致线程被阻止的情况,就显得愈加重要。
Visual Studio 2010 中的性能工具包含一种新的分析方法“资源争用分析” ,此方法有助于检测线程间的并发争用。在 John Robbins 的 Wintellect 博客 文章中,您可以看到对此功能的精彩简介,其网址为: wintellect.com/CS/blogs/jrobbins/archive/2009/10/19/vs-2010-beta-2- concurrency-resource-profiling-in-depth-first-look.aspx。
在本文中,我将演练一个争用分析调查,并讲解可以使用 Visual Studio 2010 IDE 和命令行工具收集的数据。此外,还将向您展示如何在 Visual Studio 2010 中分析数据,您会看到,在执行争用调查时,如何从一个分析视图 切换到另一个分析视图。然后,我会修改代码,并将修改过的应用程序的分析结 果与原来的分析结果进行比较,验证所做的修改确实减少了争用的数量。
从问题开始
作为示例,我将使用 Hazim Shafi 在其博客文章“性能模式 1:识别锁争用 ”(blogs.msdn.com/hshafi/archive/2009/06/19/performance-pattern-1- identifying-lock-contention.aspx) 中使用的同一个矩阵乘法应用程序。尽管 代码示例是用 C++ 编写的,但是我讨论的概念同样适用于托管代码。
示例矩阵乘法应用程序使用多个线程对两个矩阵执行乘法操作。每个线程都 将承担一部分工作,并运行以下代码段:
for (i = myid*PerProcessorChunk;
i < (myid+1)*PerProcessorChunk;
i++) {
EnterCriticalSection(&mmlock);
for (j=0; j<SIZE; j++) {
for (k=0; k<SIZE; k++) {
C[i][j] += A[i][k]*B[k][j];
}
}
LeaveCriticalSection(&mmlock);
}
每个线程都有自己的 ID (myid),并且负责使用矩阵 A 和 B 作为输入,来 计算结果矩阵 C 中的行数(一行或多行)。深入的代码检测表明,没有发生真 正引起歧义的写共享,每个线程都写入 C 的不同行。然而,开发人员还是决定 使用关键部分来保证对矩阵的赋值。我要感谢开发人员的这个决定,因为这给了 我一个好机会,来展示新的 Visual Studio 2010 性能工具能够轻松发现冗余的 同步。