线程分析器是一款工具,可用于分析多线程程序的执行情况。线程分析器可以检测多线程编程错误,例如用 POSIX 线程 API、Solaris 线程 API、OpenMP 指令或混用这几者编写的代码中的数据争用和死锁。
本章讨论以下主题:
线程分析器支持下列硬件和操作系统:
SPARC(R) v8plus、v8plusa、v8plusb、v9、v9a 和 v9b 体系结构
Intel(R) x86 和 AMD(R) x64 平台
Oracle Solaris 10 1/06 及后续更新 OpenSolaris 2008.11 和 2009.06
Oracle Enterprise Linux 5.3、SuSE Linux Enterprise Server 10、RedHat Enterprise Linux 5、CentOS 5
线程分析器可检测多线程进程执行期间发生的数据争用。满足以下所有条件时,就会发生数据争用:
一个进程内的两个或多个线程同时访问同一内存位置
至少其中一个访问是用于写入
线程未使用任何互斥锁来控制它们对该内存的访问
这三个条件成立时,访问的顺序是不确定的,每次运行的计算结果都可能因该顺序而异。有些数据争用可能是良性的(例如,内存访问用于忙等待时),但许多数据争用都属于程序中的错误。
线程分析器适用于用 POSIX 线程 API、Solaris 线程 API、OpenMP 或混用这几者编写的多线程程序。
死锁描述的是两个或多个线程由于相互等待而永远被阻塞的情况。导致死锁的原因有很多。线程分析器可检测由于不恰当使用互斥锁而导致的死锁。此类死锁通常发生在多线程应用程序中。
满足以下所有条件时,包含两个或多个线程的进程可能会进入死锁状态:
已持有锁的线程请求新锁
同时发出对新锁的请求
两个或多个线程形成一个循环链,其中每个线程都在等待链中下一个线程持有的锁
以下是一个死锁情况的简单示例:
线程 1 持有锁 A 并请求锁 B
线程 2 持有锁 B 并请求锁 A
死锁可分为两种类型:潜在死锁或实际死锁。潜在死锁不一定在给定的运行过程中发生,但可能发生在程序的任何执行过程中,具体取决于线程的调度情况以及线程进行锁请求的时间。实际死锁是在程序执行过程中发生的死锁。实际死锁会导致所涉及的线程挂起,但不一定会导致整个进程挂起。
下列步骤说明了使用线程分析器为多线程程序排除故障的过程。
校验程序(如果进行数据争用检测)。
创建数据争用检测实验或死锁检测实验。
检查实验结果,并确定线程分析器所发现的多线程编程冲突属于真实错误还是良性现象。
修复真实的错误,并使用不同的因素(如不同的输入数据、不同的线程数、不同的循环调度,乃至不同的硬件)创建其他实验(上面的步骤 2)。这种重复有助于找出原因不确定的问题。
以下几节将介绍上述的步骤 1 到步骤 3。
必须执行下列三个步骤来检测数据争用:
校验代码以允许数据争用检测
基于校验后的代码创建实验
检查实验中是否存在数据争用
为了在应用程序中检测数据争用,必须首先对代码进行校验以监视运行时的内存访问。可以在编译过程中在应用程序源代码级别校验代码,也可以通过对二进制代码运行其他工具在应用程序二进制代码级别校验代码。
源代码级别校验通过编译器完成,需要使用特殊选项。还可以指定优化级别以及要使用的其他编译器选项。由于编译器可以进行一些分析工作并校验较少的内存访问,因此源代码级别校验可使运行时速度更快。
源代码不可用时,可以使用二进制代码级别校验。有源代码时也可以使用二进制代码校验,但无法编译应用程序所使用的共享库。如果使用 discover 工具进行二进制代码校验,则可以校验二进制代码并可在所有共享库打开时校验所有这些共享库。
要在源代码级别进行校验,请使用特殊的编译器选项编译源代码:
-xinstrument=datarace |
通过使用该编译器选项,将会对编译器生成的代码进行校验以便检测数据争用。
生成应用程序二进制代码时,还应使用 -g 编译器选项。该选项会导致生成额外数据,线程分析器通过该数据可以在报告数据争用时显示源代码和行号信息。
要在二进制代码级别进行校验,必须使用 discover 工具。如果二进制代码命名为 a.out,可以通过执行以下命令创建校验后的二进制代码 a.outi:
discover -i datarace -o a.outi a.out |
discover 工具在所有共享库打开时自动校验所有这些共享库(无论它们是静态链接到程序中的,还是通过 dlopen() 动态打开的)。缺省情况下,校验后的库副本会缓存到 $HOME/SUNW_Bit_Cache 目录中。
下面显示了一些有用的 discover 命令行选项。有关详细信息,请参见 discover(1) 手册页。
将校验后的二进制代码输出到指定文件名
不校验指定的库
不校验任何库
将高速缓存目录更改为 dir
为了校验程序的二进制代码以检测数据争用,discover 工具要求在以下条件下编译输入的二进制代码:
操作系统版本至少必须为 Oracle Solaris 10 Update 5 或 OpenSolaris 版本 snv_70。
编译器必须是不低于 Oracle Solaris Studio 12 Update 1 的发行版。
必须使用编译器优化标志 (-xO1, -xO2, -xO3, -xO4, -xO5) 之一。
另外,为了使线程分析器在报告数据争用时显示源代码和行号信息,还应该使用 -g 编译器选项。
如果使用编译器选项 -xbinopt=prepare 编译了二进制代码,可能也可以在基于 SPARC 的系统中运行的早期 Solaris 版本上使用 discover 工具。有关该编译器选项的信息,请参见 cc(1)、CC(1) 或 f95(1) 手册页。
要创建数据争用检测实验,请使用带有 -r race 标志的 collect 命令运行应用程序并在进程执行期间收集实验数据。使用 -r race 选项时,收集到的数据包含构成争用的数据访问对。
可以使用 tha 命令检查数据争用检测实验,该命令会启动线程分析器图形用户界面。也可以使用 er_print 命令行界面。
检测死锁包括两个步骤:
创建死锁检测实验。
检查实验中是否存在死锁。
要创建死锁检测实验,请使用带有 -r deadlock 标志的 collect 命令运行应用程序并在进程执行期间收集实验数据。使用 -r deadlock 选项时,收集到的数据包含形成循环链的锁持有和锁请求。
可以使用 tha 命令检查死锁检测实验,该命令会启动线程分析器图形用户界面。也可以使用 er_print 命令行界面。
如果要同时检测数据争用和死锁,请按照1.4.1 检测数据争用的使用模型中的三个步骤来检测数据争用,但要使用带有 -r race,deadlock 标志的 collect 命令来运行应用程序。实验将同时包含争用检测数据和死锁检测数据。
可以使用 tha 命令启动线程分析器。
线程分析器界面是经过简化的性能分析器界面,适合进行多线程程序分析。现在不再显示常用的性能分析器标签,而是显示 "Races"(争用)、"Deadlocks"(死锁)、"Dual Source"(双源)、"Race Details"(争用详细信息)和 "Deadlock Details"(死锁详细信息)等标签。如果使用性能分析器查看多线程程序实验,将看到传统的性能分析器标签(如 "Functions"(函数)、"Callers-Callees"(调用方-被调用方)、"Disassembly"(反汇编))以及关于数据争用和死锁的标签。