目录

8.1。概观
8.1.1。多线程编程范例8.1.2。POSIX线程编程模型8.1.3。多线程编程问题8.1.4。数据竞赛检测
8.2。使用DRD
8.2.1。DRD命令行选项8.2.2。检测到的错误:数据竞赛8.2.3。检测到的错误:锁定争用8.2.4。检测到的错误:滥用POSIX线程API8.2.5。客户端请求8.2.6。调试C ++ 11程序8.2.7。调试GNOME程序8.2.8。调试Boost.Thread程序8.2.9。调试OpenMP程序8.2.10。DRD和自定义内存分配器8.2.11。DRD与Memcheck8.2.12。资源要求8.2.13。有效使用DRD的提示和提示
8.3。有效地使用POSIX线程API
8.3.1。互斥类型8.3.2。条件变量8.3.3。pthread_cond_timedwait和超时
8.4。限制8.5。反馈

要使用此工具,必须--tool=drd 在Valgrind命令行上指定 。

8.1。概观

DRD是用于检测多线程C和C ++程序中的错误的Valgrind工具。该工具适用于使用POSIX线程原语的任何程序,或者使用构建在POSIX线程图元之上的线程概念。

8.1.1。多线程编程范例

在程序中使用多线程有两个可能的原因:

  • 模拟并发活动。与单个线程中多个活动的状态复用相比,将一个线程分配给每个活动可以是一个很好的简化。这就是为什么大多数服务器软件和嵌入式软件都是多线程的。

  • 同时使用多个CPU内核来加速计算。这就是为什么许多高性能计算(HPC)应用程序是多线程的。

多线程程序可以使用以下一个或多个编程范例。哪个范例适合取决于应用程序类型。多线程编程范例的一些例子是:

  • 锁定。通过线程共享的数据可以通过锁定进行并发访问。例如POSIX线程库,Qt库和Boost.Thread库直接支持该范例。

  • 消息传递。线程之间没有共享数据,但线程通过相互传递消息来交换数据。消息传递范例的实现示例是MPI和CORBA。

  • 自动并行化 编译器将顺序程序转换为多线程程序。原始程序可能包含也可能不包含并行化提示。这种并行化提示的一个例子是OpenMP标准。在本标准中,定义了一组指令,告诉编译器如何并行化C,C ++或Fortran程序。OpenMP非常适合于计算密集型应用。例如,开源图像处理软件包正在使用OpenMP来最大化具有多个CPU内核的系统的性能。GCC支持4.2.0版的OpenMP标准。

  • 软件事务内存(STM)。通过事务更新线程之间共享的任何数据。每个交易之后,验证是否存在任何冲突的交易。如果存在冲突,则该事务被中止,否则就被提交。这是一种所谓的乐观态度。有一个支持STM的Intel C ++编译器的原型。关于向海湾合作委员会增加STM支持的研究正在进行之中。

只要这些范例的实现基于POSIX线程图元,DRD就支持多线程编程范例的任何组合。然而,DRD不支持直接使用例如Linux'futexes的程序。尝试用DRD分析这些程序将导致DRD报告许多假阳性。

8.1.2。POSIX线程编程模型

POSIX线程,也称为Pthreads,是Unix系统上最广泛使用的线程库。

POSIX线程编程模型基于以下抽象:

  • 共享地址空间。在同一进程内运行的所有线程共享相同的地址空间。所有数据(无论是否共享)都由其地址标识。

  • 定期加载和存储操作,允许从同一进程中运行的所有线程共享的内存读取值或向其写入值。

  • 原子存储和加载修改存储操作。虽然这些在POSIX线程标准中没有提及,但大多数微处理器都支持原子内存操作。

  • 线程。每个线程表示并发活动。

  • 同步对象和这些同步对象的操作。在POSIX线程标准中定义了以下类型的同步对象:互斥体,条件变量,信号量,读写器同步对象,障碍和自旋锁。

哪些源代码语句生成哪些内存访问取决于正在使用的编程语言的内存模型。C和C ++语言还没有一个确定的内存模型。对于内存模型草案,另见文档 WG21 / N2338:并发内存模型编译器的后果。

有关POSIX线程的更多信息,另请参阅单一UNIX规范版本3,也称为 IEEE Std 1003.1。

8.1.3。多线程编程问题

根据程序中使用的多线程范例,可能会出现以下一个或多个问题:

  • 数据竞赛 一个或多个线程访问相同的内存位置,而没有足够的锁定。大多数但不是所有的数据竞赛都是编程错误,并且是微妙和难以发现的错误的原因。

  • 锁定争用 一个线程通过持有锁太长来阻止一个或多个其他线程的进度。

  • 不正确的使用POSIX线程API。POSIX线程API的大多数实现已针对运行速度进行了优化。这样的实现不会对某些错误抱怨,例如当互斥体被另一个线程解锁时,而不是获得互斥锁上的锁的线程。

  • 僵局。当两个或多个线程无限期地等待彼此时,会发生死锁。

  • 虚假分享 如果在不同处理器核心上运行的线程经常访问位于同一高速缓存行中的不同变量,那么由于高速缓存线的频繁交换,这将减慢涉及的线程。

尽管数据竞赛发生的可能性可以通过有纪律的编程风格来降低,但是在开发多线程软件时,必须使用自动检测数据竞赛的工具。DRD可以检测这些,以及锁定争用和不正确使用POSIX线程API。

8.1.4。数据竞赛检测

由多线程程序执行的加载和存储操作的结果取决于执行内存操作的顺序。此订单由以下决定:

  1. 由同一线程执行的所有存储器操作都按 程序顺序执行,即由程序源代码确定的顺序和先前加载操作的结果。

  2. 同步操作确定由不同线程执行的存储器操作的某些排序约束。这些排序约束称为同步顺序

程序顺序和同步顺序的组合称为 发生之前的关系。这一概念首先由S.Adve等人在“ 检测弱记忆体系数据竞赛”(ACM SIGARCH Computer Architecture News,v.19 n.3,p.234-243,1991年5月)中定义。

如果两个操作都是由不同的线程执行的,则 两个内存操作冲突,指的是相同的内存位置,其中至少有一个是存储操作。

如果所有冲突的存储器访问是通过同步操作排序的,则多 线程程序是数据竞争的

确保多线程程序无数据资源的众所周知的方法是确保遵守锁定规则。例如,可以将互斥体与每个共享数据项相关联,并且在访问共享数据时对相关联的互斥体进行锁定。

遵循锁定规则的所有程序都是数据竞赛的,但并不是所有无数据竞争的程序都遵循锁定规则。存在多线程程序,其中通过条件变量,信号量或障碍来对共享数据进行访问。作为一个例子,一类HPC应用程序由一系列计算步骤组成,这些步骤在时间上由障碍隔开,而这些障碍是唯一的同步手段。尽管在这些应用程序中存在许多冲突的存储器访问,尽管这样的应用程序不能使用互斥体,但是这些应用程序大多数不包含数据种族。

在运行时验证多线程程序的正确性存在两种不同的方法。所谓的擦除算法的方法是验证所有共享存储器访问是否遵循一致的锁定策略。并且发生之前的数据竞争检测器直接验证所有的连接间存储器访问是否通过同步操作排序。虽然最后一种方法实现起来更加复杂,虽然它对OS调度更加敏感,但它是一种通用的方法,适用于所有类的多线程程序。发生在数据竞争检测器之前的一个重要优点是这些不报告任何误报。

DRD是基于之前的算法。

8.2。使用DRD

8.2.1。DRD命令行选项

以下命令行选项可用于控制DRD工具本身的行为:

--check-stack-var=<yes|no> [default: no]

控制DRD是否检测堆栈变量上的数据竞争。默认情况下禁用验证堆栈变量,因为大多数程序不会通过线程共享堆栈变量。

--exclusive-threshold=<n> [default: off]

如果任何互斥锁或写卡器锁定的时间长于指定的时间(毫秒),则打印错误消息。此选项可以检测锁争用。

--join-list-vol=<n> [default: 10]

如果内存访问信息在线程加入后立即被丢弃,则可能会错过在一个线程的结尾处的语句和另一个线程之间发生的数据竞争。此选项允许指定应保留多少连接的线程内存访问信息。

--first-race-only=<yes|no> [default: no]

是否仅报告在内存位置检测到的第一个数据竞赛或在内存位置检测到的所有数据竞赛。

--free-is-write=<yes|no> [default: no]

是否报告访问内存和释放内存之间的比赛。启用此选项可能会导致DRD运行速度稍慢。笔记:

  • 使用自定义内存分配器时,请勿启用此选项VG_USERREQ__MALLOCLIKE_BLOCK ,VG_USERREQ__FREELIKE_BLOCK 因为会导致误报。

  • 使用引用计数的对象时,不要启用该选项,因为这将导致误报,即使该代码已与正确注释 ANNOTATE_HAPPENS_BEFORE 和ANNOTATE_HAPPENS_AFTER。有关示例,请参见以下命令的输出: valgrind --tool=drd --free-is-write=yes drd/tests/annotate_smart_pointer

--report-signal-unlocked=<yes|no> [default: yes]

是否在信号发送时通过或 未被锁定来报告与该信号相关联的互斥体的呼叫 pthread_cond_signal和 pthread_cond_broadcast在哪里 。发送一个信号而不用锁定相关的互斥体是常见的编程错误,可能导致微妙的竞争条件和不可预知的行为。存在一些不寻常的同步模式,但是可以安全地发送信号而不对相关联的互斥锁进行锁定。 pthread_cond_waitpthread_cond_timed_wait

--segment-merging=<yes|no> [default: yes]

控制段合并。段合并是一种限制数据竞争检测算法的内存使用的算法。禁用段合并可能提高竞赛报告中显示的所谓“其他段”的准确性,但也可能会触发内存不足错误。

--segment-merging-interval=<n> [default: 10]

仅在创建指定数量的新细分后才执行细分合并。这是一个高级配置选项,可以选择是否通过选择较低的值来最小化DRD的内存使用率,或者通过选择略高的值来让DRD运行得更快。该参数的最佳值取决于正在分析的程序。默认值适用于大多数程序。

--shared-threshold=<n> [default: off]

如果读卡器锁定时间超过指定时间(以毫秒为单位),则打印错误消息。此选项可以检测锁争用。

--show-confl-seg=<yes|no> [default: yes]

在比赛报告中显示相互冲突的细分。由于此信息可以帮助您找到数据竞争的原因,默认情况下会启用此选项。禁用此选项可使DRD的输出更加紧凑。

--show-stack-usage=<yes|no> [default: no]

在线程退出时打印堆栈使用情况。当程序创建大量线程时,限制为线程堆栈分配的虚拟内存量变得非常重要。该选项使得可以观察到客户端程序的每个线程已经使用了多少堆栈内存。注意:DRD工具本身在客户端线程堆栈上分配一些临时数据。这个临时数据所需的空间必须由客户端程序分配堆栈内存,但不包括在DRD报告的堆栈使用中。

--ignore-thread-creation=<yes|no> [default: no]

控制线程创建过程中是否应忽略所有活动。默认情况下仅在Solaris上启用。Solaris提供了比其他操作系统更高的吞吐量,并行性和可扩展性,代价是更细粒度的锁定活动。这意味着例如,当在glibc下创建一个线程时,所有线程设置只使用一个大锁。Solaris libc使用几个细粒度的锁,并且创建者线程尽快恢复其活动,例如堆栈和TLS设置顺序到创建的线程。这种情况混淆了DRD,因为它假定在创建者和创建的线程之间存在一些错误的顺序; 因此,不会报告申请中的许多类型的种族条件。 yes为了防止这种错误排序, 在Solaris上默认情况下将此命令行选项设置为。所有活动(加载,存储,客户端请求)因此在以下期间被忽略:

  • pthread_create()在创建者线程中调用

  • 线程创建阶段(堆栈和TLS设置)在创建的线程中

以下选项可用于监视客户端程序的行为:

--trace-addr=<address> [default: none]

跟踪指定地址的所有加载和存储活动。此选项可以被指定多次。

--ptrace-addr=<address> [default: none]

跟踪指定地址的所有加载和存储活动,并保持这样做,即使在该地址的内存已被释放并重新分配。

--trace-alloc=<yes|no> [default: no]

跟踪所有内存分配和释放。可能产生大量的产量。

--trace-barrier=<yes|no> [default: no]

跟踪所有障碍活动。

--trace-cond=<yes|no> [default: no]

跟踪所有条件变量活动。

--trace-fork-join=<yes|no> [default: no]

跟踪所有线程创建和所有线程终止事件。

--trace-hb=<yes|no> [default: no]

跟踪的执行ANNOTATE_HAPPENS_BEFORE(), ANNOTATE_HAPPENS_AFTER()以及 ANNOTATE_HAPPENS_DONE()客户端请求。

--trace-mutex=<yes|no> [default: no]

跟踪所有互斥体活动。

--trace-rwlock=<yes|no> [default: no]

跟踪所有读写器锁定活动。

--trace-semaphore=<yes|no> [default: no]

跟踪所有信号量活动。

8.2.2。检测到的错误:数据竞赛

每次检测到数据竞赛时,DRD会打印一条消息。在解释DRD的输出时,请记住以下几点:

  • 每个线程由DRD工具分配一个线程ID。线程ID是一个数字。线程ID从一开始就永远不会被回收。

  • 术语是指连续的负载,存储和同步操作序列,全部由同一个线程发出。段始终在同步操作中开始和结束。由于性能原因,在段之间进行数据竞争分析,而不是在单独的负载和存储操作之间进行。

  • 在数据竞赛中总是至少有两个内存访问。涉及数据竞争的内存访问称为 冲突存储器访问。DRD打印一份与过去内存访问冲突的每个内存访问的报告。

下面您可以找到DRD在检测到数据竞赛时打印的消息的示例:

$ valgrind --tool = drd --read-var-info = yes drd / tests / rwlock_race
...
== 9466 ==主题3:
== 9466 ==线程3在0x006020b8大小的冲突负载4
== 9466 ==在0x400B6C:thread_func(rwlock_race.c:29)
== 9466 == by 0x4C291DF:vg_thread_wrapper(drd_pthread_intercepts.c:186)
== 9466 == by 0x4E3403F:start_thread(in /lib64/libpthread-2.8.so)
== 9466 == by 0x53250CC:clone(in /lib64/libc-2.8.so)
== 9466 ==位置0x6020b8是本地var“s_racy”内的0个字节
== 9466 ==在rwlock_race.c中声明:18,在线程3的框架#0中
== 9466 ==其他段启动(线程2)
== 9466 == at 0x4C2847D:pthread_rwlock_rdlock *(drd_pthread_intercepts.c:813)
== 9466 == by 0x400B6B:thread_func(rwlock_race.c:28)
== 9466 == by 0x4C291DF:vg_thread_wrapper(drd_pthread_intercepts.c:186)
== 9466 == by 0x4E3403F:start_thread(in /lib64/libpthread-2.8.so)
== 9466 == by 0x53250CC:clone(in /lib64/libc-2.8.so)
== 9466 ==其他段末(线程2)
== 9466 == at 0x4C28B54:pthread_rwlock_unlock *(drd_pthread_intercepts.c:912)
== 9466 == by 0x400B84:thread_func(rwlock_race.c:30)
== 9466 == by 0x4C291DF:vg_thread_wrapper(drd_pthread_intercepts.c:186)
== 9466 == by 0x4E3403F:start_thread(in /lib64/libpthread-2.8.so)
== 9466 == by 0x53250CC:clone(in /lib64/libc-2.8.so)
...

上述报告具有以下含义:

  • 左侧列中的数字是由DRD分析的进程的进程ID。

  • 第一行(“线程3”)告诉您线程的线程ID,在哪个上下文中检测到数据竞争。

  • 下一行告诉执行哪种操作(加载或存储)以及哪个线程。同一行也显示冲突访问中涉及的起始地址和字节数。

  • 接下来,显示冲突访问的调用堆栈。如果您的程序已使用调试信息(-g)进行编译,则此调用堆栈将包含文件名和行号。此调用堆栈(clone 和start_thread)中的两个最底层框架显示了NPTL如何启动一个线程。第三帧(vg_thread_wrapper)由DRD添加。第四帧(thread_func)是第一个有趣的行,因为它显示了线程入口点,即作为第三个参数传递的函数 pthread_create

  • 接下来,显示冲突地址的分配上下文。对于动态分配的数据,显示分配调用堆栈。对于静态变量和堆栈变量,只有在指定了该选项时才会显示分配上下文 --read-var-info=yes。否则DRD将打印Allocation context: unknown

  • 冲突访问涉及至少两个内存访问。对于这些访问之一,显示精确的调用堆栈,并且对于其他访问,显示近似调用堆栈,即其他访问段的开始和结束。该信息可以解释如下:

    1. 从两个调用堆栈的底部开始,并对具有相同功能名称,文件名和行号的数量堆栈帧进行计数。在上述例子中三个最下面的帧是相同的(clone, start_thread和 vg_thread_wrapper)。

    2. 两个调用堆栈中的下一个更高的堆栈帧现在告诉您在哪个源代码区域中发生了其他内存访问。上述输出告诉数据竞争中涉及的其他内存访问发生在文件中的源代码行28和30之间 rwlock_race.c

8.2.3。检测到的错误:锁定争用

线程必须能够进行而不被其他线程阻塞太久。有时,线程必须等到互斥体或读写器同步对象被另一个线程解锁。这被称为 锁争用

锁争用导致延迟。这种拖延应尽可能短。两个命令行选项 --exclusive-threshold=<n>,并 --shared-threshold=<n>通过使DRD报告任何持续时间超过指定阈值的锁,可以检测到过多的锁争用。一个例子:

$ valgrind --tool = drd --exclusive-threshold = 10 drd / tests / hold_lock -i 500
...
== 10668 ==收购于:
== 10668 == at 0x4C267C8:pthread_mutex_lock(drd_pthread_intercepts.c:395)
== 10668 == by 0x400D92:main(hold_lock.c:51)
== 10668 ==锁定互斥量0x7fefffd50在503 ms(阈值:10 ms)内保持。
== 10668 == at 0x4C26ADA:pthread_mutex_unlock(drd_pthread_intercepts.c:441)
== 10668 == 0x400DB5:main(hold_lock.c:55)
...

hold_lock只要由-i(interval)参数指定 ,测试程序就会保存一个锁。DRD输出报告在源文件中在线路51获取hold_lock.c并在线55释放的锁定在 503ms内保持,而对DRD指定了10ms的阈值。

8.2.4。检测到的错误:滥用POSIX线程API

DRD能够检测并报告POSIX线程API的以下滥用情况:

  • 将一种类型的同步对象(例如互斥体)的地址传递到期望具有指向另一类型同步对象(例如条件变量)的指针的POSIX API调用。

  • 尝试解锁未锁定的互斥体。

  • 尝试解锁被另一个线程锁定的互斥体。

  • 尝试PTHREAD_MUTEX_NORMAL递归地锁定类型或螺旋锁的互斥体 。

  • 锁定的互斥体的销毁或释放。

  • 将信号发送到条件变量,而与条件变量相关联的互斥体上没有锁定。

  • 调用pthread_cond_wait未锁定的互斥体,被另一个线程锁定或递归锁定的互斥体。

  • 将两个不同的互斥体与条件变量相关联pthread_cond_wait

  • 正在等待的条件变量的销毁或释放。

  • 锁定的读写器同步对象的销毁或释放。

  • 尝试解锁未被调用线程锁定的读写器同步对象。

  • 尝试递归地锁定读写器同步对象。

  • 尝试将用户定义的读写器同步对象的地址传递给POSIX线程函数。

  • 尝试将POSIX读写器同步对象的地址传递给用户定义的读写器同步对象的注释之一。

  • 互斥体,条件变量,读写器锁定,信号量或屏障的重新初始化。

  • 正在等待的信号灯或屏障的销毁或释放。

  • 屏障等待和屏障破坏之间缺少同步。

  • 退出一个线程,而不会首先解锁该线程锁定的自旋锁,互斥锁或读写器同步对象。

  • 将无效的线程ID传递给pthread_join 或pthread_cancel

8.2.5。客户端请求

就像其他Valgrind工具一样,有可能让客户端程序通过客户端请求与DRD工具进行交互。除了客户端请求之外,还定义了一些允许以便利的方式使用客户端请求的宏。

头文件中定义了客户端程序与DRD工具之间的接口<valgrind/drd.h>。可用的宏和客户端请求是:

  • DRD_GET_VALGRIND_THREADID和相应的客户端请求VG_USERREQ__DRD_GET_VALGRIND_THREAD_ID。查询由Valgrind核心分配给执行此客户端请求的线程的线程ID。Valgrind的线程ID从一个开始,如果线程停止则被回收。

  • DRD_GET_DRD_THREADID和相应的客户端请求VG_USERREQ__DRD_GET_DRD_THREAD_ID。查询由DRD分配给执行此客户端请求的线程的线程ID。这些是DRD在数据竞赛报告和跟踪消息中报告的线程ID。DRD的线程ID从一开始就永远不会被回收。

  • DRD_IGNORE_VAR(x), ANNOTATE_TRACE_MEMORY(&x)和相应的客户端请求VG_USERREQ__DRD_START_SUPPRESSION。一些应用程序包含有意的种族。存在例如从两个不同线程将共享变量分配给相同值的应用程序。抑制这种种族可能比解决这些种族更为方便。该客户端请求允许抑制这种种族。

  • DRD_STOP_IGNORING_VAR(x)和相应的客户端请求 VG_USERREQ__DRD_FINISH_SUPPRESSION。告诉DRD不再忽略通过宏DRD_IGNORE_VAR(x)或通过客户端请求抑制的地址范围的数据竞争VG_USERREQ__DRD_START_SUPPRESSION

  • 这个宏DRD_TRACE_VAR(x)。追踪从起始&x和占用sizeof(x)字节开始的地址范围的所有加载和存储活动。当DRD在指定的变量上报告数据竞争时,并不立即清楚哪些源代码语句触发了冲突的访问,可能非常有助于跟踪违规内存位置上的所有活动。

  • 这个宏DRD_STOP_TRACING_VAR(x)。停止跟踪地址范围的加载和存储活动,&x并占用sizeof(x) 字节。

  • 这个宏ANNOTATE_TRACE_MEMORY(&x)。跟踪至少触及地址单个字节的所有加载和存储活动&x

  • 客户端请求VG_USERREQ__DRD_START_TRACE_ADDR,允许跟踪指定地址范围的所有加载和存储活动。

  • 客户端请求VG_USERREQ__DRD_STOP_TRACE_ADDR。不再跟踪指定地址范围的加载和存储活动。

  • ANNOTATE_HAPPENS_BEFORE(addr)告诉DRD插入一个标记。在执行对指定地址的变量的访问之后插入此宏。

  • 该宏ANNOTATE_HAPPENS_AFTER(addr)告诉DRD,指定地址上的变量的下一次访问应该被认为是在ANNOTATE_HAPPENS_BEFORE(addr)引用同一变量的最新注释之前的访问之后发生的 。这两个宏的目的是告诉DRD通过原子内存操作实现的线程间内存访问顺序。另见drd/tests/annotate_smart_pointer.cpp一个例子。

  • ANNOTATE_RWLOCK_CREATE(rwlock)告诉DRD地址rwlock上的对象是不是pthread_rwlock_t同步对象的读写器 同步对象。另见drd/tests/annotate_rwlock.c一个例子。

  • 该宏ANNOTATE_RWLOCK_DESTROY(rwlock)告诉DRD地址处的读写器同步对象rwlock已被破坏。

  • ANNOTATE_WRITERLOCK_ACQUIRED(rwlock)告诉DRD已经在地址处的读写器同步对象上获取了写入器锁rwlock

  • 该宏ANNOTATE_READERLOCK_ACQUIRED(rwlock)告诉DRD已经在地址处的读写器同步对象上获取了读取器锁定rwlock

  • ANNOTATE_RWLOCK_ACQUIRED(rwlock, is_w) 告诉DRD 在地址处的读写器同步对象上已经获取了写入器锁定(何时is_w != 0)或读取器锁定(何时is_w == 0rwlock

  • ANNOTATE_WRITERLOCK_RELEASED(rwlock)告诉DRD地址上的读写器同步对象已经释放了写入器锁rwlock

  • 该宏ANNOTATE_READERLOCK_RELEASED(rwlock)告诉DRD在地址处的读写器同步对象上已经释放了读取器锁定rwlock

  • ANNOTATE_RWLOCK_RELEASED(rwlock, is_w) 告诉DRD写入器锁定(何时is_w != 0)或读取器锁定(何时is_w == 0)已在地址处的读写器同步对象上释放rwlock

  • 该宏ANNOTATE_BARRIER_INIT(barrier, count, reinitialization_allowed)告诉DRD,地址处的一个新的屏障对象barrier已被初始化,该count线程参与每个屏障,以及是否将没有中间破坏的屏障重新初始化作为错误报告。另见drd/tests/annotate_barrier.c一个例子。

  • ANNOTATE_BARRIER_DESTROY(barrier) 告诉DRD屏障对象即将被销毁。

  • 该宏ANNOTATE_BARRIER_WAIT_BEFORE(barrier) 告诉DRD,等待屏障将开始。

  • ANNOTATE_BARRIER_WAIT_AFTER(barrier) 指示DRD等待屏障已经完成。

  • ANNOTATE_BENIGN_RACE_SIZED(addr, size, descr)告诉DRD,在指定地址上检测到的任何种族是良性的,因此不应该报告。该descr参数被忽略,但可用于记录为什么数据竞赛addr是良性的。

  • ANNOTATE_BENIGN_RACE_STATIC(var, descr) 告诉DRD,在指定的静态变量上检测到的任何种族都是良性的,因此不应该报告。该descr 参数被忽略,但可用于记录为什么数据竞赛var是良性的。注意:这个宏只能在C ++程序中使用,而不能在C程序中使用。

  • ANNOTATE_IGNORE_READS_BEGIN告诉DRD忽略当前线程执行的所有内存加载。

  • ANNOTATE_IGNORE_READS_END告诉DRD停止忽略当前线程执行的内存负载。

  • ANNOTATE_IGNORE_WRITES_BEGIN告诉DRD忽略当前线程执行的所有内存存储。

  • ANNOTATE_IGNORE_WRITES_END告诉DRD停止忽略当前线程执行的内存存储。

  • ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN告诉DRD忽略当前线程执行的所有内存访问。

  • ANNOTATE_IGNORE_READS_AND_WRITES_END告诉DRD停止忽略当前线程执行的内存访问。

  • 该宏ANNOTATE_NEW_MEMORY(addr, size)告诉DRD客户端程序中的自定义内存分配器分配了指定的内存范围,并且客户端程序将开始使用此内存范围。

  • ANNOTATE_THREAD_NAME(name)告诉DRD将指定的名称与当前线程相关联,并将此名称包含在DRD打印的错误消息中。

  • VALGRIND_MALLOCLIKE_BLOCK和 VALGRIND_FREELIKE_BLOCK从Valgrind核心实现; 它们在 客户端请求机制中描述。

注意:如果您自己编译Valgrind,则该命令 <valgrind/drd.h>将头文件 安装在目录中。如果您通过将其安装为一个包获得Valgrind,那么您可能必须安装另一个包含 Valgrind头文件可用的名称。 /usr/includemake installvalgrind-devel

8.2.6。调试C ++ 11程序

如果要使用C ++ 11类std :: thread,则需要执行以下操作来注释在该类的实现中使用的std :: shared_ptr <>对象:

  • 在包含任何C ++头文件之前,在公共头的起始处或每个源文件的开始添加以下代码:

    #include <valgrind / drd.h>
    #define _GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(addr)ANNOTATE_HAPPENS_BEFORE(addr)
    #define _GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(addr)ANNOTATE_HAPPENS_AFTER(addr)
    
  • 下载gcc源代码,从源文件libstdc ++ - v3 / src / c ++ 11 / thread.cc将执行 execute_native_thread_routine() 和std::thread::_M_start_thread() 函数复制到与应用程序链接的源文件中。确保在此源文件中也可以正确定义_GLIBCXX_SYNCHRONIZATION_HAPPENS _ *()宏。

有关更多信息,请参阅“GNU C ++库手册”,“调试支持” (http://gcc.gnu.org/onlineocs/libstdc++/manual/debug.html)。

8.2.7。调试GNOME程序

GNOME应用程序使用由提供的线程原语 glib和 gthread图书馆。这些库构建在POSIX线程之上,因此由DRD直接支持。请记住,g_thread_init在创建任何线程之前必须先调用 ,否则DRD会报告glib函数上的几个数据比赛。又见 GLib的参考手册有关详细信息 g_thread_init

glib 图书馆 提供的许多设施之一是一个块分配器,称为g_slice。在使用DRD时,必须通过将以下内容添加到shell环境变量来禁用此块分配器: G_SLICE=always-malloc。有关更多信息,请参阅GLib参考手册。

8.2.8。调试Boost.Thread程序

Boost.Thread库是跨平台Boost库附带的线程库。这个线程库是即将推出的C ++ 0x线程库的早期实现。

使用Boost.Thread库的应用程序在DRD下运行良好。

有关Boost.Thread的更多信息,请访问:

  • Anthony Williams,Boost.Thread Library Documentation,Boost website,2007。

  • 安东尼·威廉姆斯,Boost Threads的新功能?,最新修改的Boost Thread库,Dobbs博士,2008年10月。

8.2.9。调试OpenMP程序

OpenMP代表开放多重处理。OpenMP标准包括一组用于C,C ++和Fortran程序的编译器指令,允许编译器将顺序程序转换为并行程序。OpenMP非常适合HPC应用程序,并允许在直接使用POSIX线程API的情况下在更高级别工作。虽然OpenMP确保POSIX API被正确使用,但OpenMP程序仍然可以包含数据竞争。因此,使用线程检查工具验证OpenMP程序是绝对有意义的。

DRD支持GCC生成的OpenMP共享内存程序。GCC自版本4.2.0起支持OpenMP。GCC对OpenMP程序的运行时支持由一个名为“库”的库提供 libgomp。在该库中实现的同步原语使用Linux'futex系统调用直接,除非该库已配置该 --disable-linux-futex选项。DRD仅支持已使用此选项配置的libgomp库,并且其中存在符号信息。对于大多数Linux发行版,这意味着您必须重新编译GCC。有关drd/scripts/download-and-build-gcc如何编译GCC的示例,请参阅Valgrind源代码树中的脚本 。libgomp.so当OpenMP程序启动时,您还必须确保新编译的 库已加载。

导出LD_LIBRARY_PATH =〜/ gcc-4.4.0 / lib64:〜/ gcc-4.4.0 / lib:

例如,drd/tests/omp_matinv当在命令行上指定了选项-r时,测试OpenMP测试程序会 触发数据竞争。数据竞赛由以下代码触发:

#pragma omp parallel for private(j)
for(j = 0; j <rows; j ++)
{if(i!= j){const elem_t factor = a [j * cols + i];for(k = 0; k <cols; k ++){a [j * cols + k]  -  = a [i * cols + k] *因子;}}
}

上面的代码是racy,因为变量k没有被声明为private。DRD将打印以下代码的以下错误消息:

$ valgrind --tool = drd --check-stack-var = yes --read-var-info = yes drd / tests / omp_matinv 3 -t 2 -r
...
线程1/1在0x7fefffbc4大小的冲突存储4在0x4014A0:gj.omp_fn.0(omp_matinv.c:203)通过0x401211:gj(omp_matinv.c:159)by 0x40166A:invert_matrix(omp_matinv.c:238)通过0x4019B4:main(omp_matinv.c:316)
位置0x7fefffbc4是本地var“k”内的0个字节
在omp_matinv.c中声明:160,在线程1的帧#0中
...

在上述输出中,函数名称gj.omp_fn.0 由GCC从函数名生成 gj。分配上下文信息显示数据竞争是由修改变量引起的k

注意:对于4.4.0之前的GCC版本,不显示分配上下文信息。使用这些GCC版本,上述输出中最可用的信息是源文件名和检测到数据竞争的行号(omp_matinv.c:203)。

有关OpenMP的更多信息,另请参阅 openmp.org。

8.2.10。DRD和自定义内存分配器

DRD跟踪通过标准的内存分配和释放功能发生的所有内存分配事件(mallocfree, newdelete),通过进入和已经标注了Valgrind的内存池的客户端请求的堆栈帧或退出。DRD使用内存分配和释放信息用于两个目的:

  • 要知道尚未明确销毁的POSIX对象的范围结束。例如POSIX线程标准pthread_mutex_destroy在释放互斥体对象所在的内存之前不需要调用 。

  • 要知道变量的范围在哪里结束。如果一个线程使用了堆内存,该线程将释放该内存,另一个线程分配并启动该内存,则不得为该内存报告数据竞争。

DRD的正确操作对工具知道内存分配和释放事件至关重要。当分析与DRD客户端程序,使用自定义的内存分配器,无论是仪表自定义的内存分配器与VALGRIND_MALLOCLIKE_BLOCK 和VALGRIND_FREELIKE_BLOCK宏或禁用定制的内存分配器。

例如,GNU libstdc ++库可以通过设置环境变量来配置为使用标准内存分配函数而不是内存池 GLIBCXX_FORCE_NEW。有关更多信息,另请参阅libstdc ++手册。

8.2.11。DRD与Memcheck

DRD的正确操作对于客户机程序中没有内存错误,如悬挂指针是至关重要的。这意味着在使用DRD进行分析之前,确保您的程序是Memcheck-clean是一个好主意。然而,有些Memcheck报告可能是由数据竞赛造成的。在这种情况下,在Memcheck之前运行DRD是有意义的。

那么应该先运行哪个工具?如果DRD和Memcheck都抱怨程序,可能的方法是在每次运行每个工具之后交替运行两个工具并修复尽可能多的错误,直到两个工具都没有打印任何更多的错误消息。

8.2.12。资源要求

DRD对堆栈和堆栈内存的要求以及客户端程序执行时间的影响如下:

  • 在DRD下使用默认DRD选项运行程序时,与客户端程序的本地运行相比,需要1.1到3.6倍的内存。如果加载了调试信息(--read-var-info=yes),则需要更多的内存。

  • DRD在客户端程序线程的堆栈上分配一些临时数据结构。此数据量限制在1 - 2 KB。确保线程堆栈足够大。

  • 大多数应用程序在DRD下的运行速度要比本机单线程运行速度慢20到50倍。对于执行频繁互斥锁定/解锁操作的应用程序,减速将最为显着。

8.2.13。有效使用DRD的提示和提示

使用DRD时,以下信息可能会有所帮助:

  • 确保调试信息存在于正在分析的可执行文件中,以便DRD可以在堆栈跟踪中打印功能名称和行​​号信息。大多数编译器可以通过编译器选项来提供调试信息 -g

  • 编译选项-O1而不是 -O0。这将减少生成的代码量,可能会减少调试信息的数量,并将加快DRD处理客户端程序的速度。有关详细信息,请参阅入门指南。

  • 如果DRD报告了作为Linux发行版一部分的库的任何错误,例如,libc.so或者 libstdc++.so安装这些库的调试包将使DRD的输出更加详细。

  • 当使用C ++时,不要将输出从多个线程发送到 std::cout。这样做不仅可以生成多个数据竞赛报告,还可能导致多个线程的输出混淆。使用 printf或执行以下操作:

    1. 派生一个类std::ostreambuf ,让该类逐行发送输出 stdout。这将避免不同线程生成的各行文本混淆。

    2. std::ostream 为每个线程创建一个实例。这使流格式设置线程本地。传递从每个实例的构造函数派生的类的每个线程的std::ostreambuf实例。

    3. 让每个线程将其输出发送到自己的实例 std::ostream而不是 std::cout

8.3。有效地使用POSIX线程API

8.3.1。互斥类型

单一UNIX规范版本二定义了以下四种互斥体类型(另请参见pthread_mutexattr_settype:文档):

  • 正常,这意味着不执行错误检查,并且互斥量是非递归的。

  • 错误检查,这意味着互斥体是非递归的,并且执行错误检查。

  • 递归,这意味着互斥体可能被递归锁定。

  • 默认,这意味着错误检查行为是未定义的,并且递归锁定的行为也是未定义的。或者:可移植代码不能通过Pthreads API触发错误条件,也不得递归地锁定默认类型的互斥体。

在复杂的应用中,事先并不总是清楚地将递归锁定哪些互斥锁,哪些互斥不会被递归地锁定。尝试递归地锁定非递归互斥将导致在没有线程检查工具的情况下很难找到的竞争条件。所以要么使用错误检查互斥体类型,并一直检查Pthread API互斥调用的返回值,或者使用递归互斥体类型。

8.3.2。条件变量

条件变量允许一个线程唤醒一个或多个其他线程。条件变量通常用于通知一个或多个线程关于共享数据的状态变化。不幸的是,通过使用条件变量作为状态信息传播的唯一手段来引入竞争条件是非常容易的。一个更好的方法是让线程轮询由互斥体保护的状态变量的更改,并将条件变量仅用作线程唤醒机制。另请参见源文件 drd/tests/monitor_example.cpp,了解如何在C ++中实现这一概念。该示例中使用的监视器概念是一个众所周知的非常有用的概念 - 有关监视器 概念的更多信息,另请参见维基百科。

8.3.3。pthread_cond_timedwait和超时

历史上,该函数 pthread_cond_timedwait仅允许规定绝对超时,这是一个与调用此函数时间无关的超时。但是,几乎每次调用此函数都表示相对超时。这通常是通过传递总和 clock_gettime(CLOCK_REALTIME)和相对超时作为第三个参数。这种方法是不正确的,因为例如ntpd的前向或后向时钟调整将影响超时。一个更可靠的方法如下:

  • 当初始化条件变量时 pthread_cond_init,指定超时 pthread_cond_timedwait将使用时钟 CLOCK_MONOTONIC而不是 CLOCK_REALTIME。你可以这样做 pthread_condattr_setclock(..., CLOCK_MONOTONIC)

  • 当调用pthread_cond_timedwait时,将总和 clock_gettime(CLOCK_MONOTONIC) 和相对超时作为第三个参数。

另见 drd/tests/monitor_example.cpp一个例子。

8.4。限制

DRD目前有以下限制:

  • DRD就像Memcheck一样,将拒绝在已经删除所有符号信息的Linux发行版上启动 ld.so。这是例如OpenPCS和Gentoo的PPC版本。在使用DRD之前,您必须在这些平台上安装glibc debuginfo软件包。另请参见openSUSE bug 396197和Gentoo bug 214065。

  • 使用gcc 4.4.3和之前,DRD可能会std::string在多线程程序中的C ++类上报告数据竞争。这是一个知道的libstdc++问题 - 更多信息,请参阅GCC错误 40518 。

  • 如果您自己编译DRD源代码,则需要GCC 3.0或更高版本。不支持GCC 2.95。

  • 在Linux的两个POSIX线程实现中,只支持NPTL(Native POSIX线程库)。较旧的LinuxThreads库不受支持。

8.5。反馈

如果您有关于DRD的任何意见,建议,反馈或错误报告,请随时在Valgrind用户邮件列表上发布消息或提交错误报告。又见http://www.valgrind.org/以获取更多信息。

DRD:线程错误检测器相关推荐

  1. 【linux】Valgrind工具集详解(十三):DRD(线程错误检测器)

    一.概述 多线程编程需要注意的问题: 数据竞争:锁竞争:POSIX线程API使用不当:死锁: 二.使用 1.例子main.c源码 #include <stdio.h> #include & ...

  2. 【linux】Valgrind工具集详解(十三):Helgrind(线程错误检测器)

    一.概述 Helgrind用于检测C.C ++和Fortran程序中使用符合POSIX标准的线程函数造成的同步错误. POSIX中关于线程的主要抽象描述有:共享公共地址空间的一组线程.线程创建.线程连 ...

  3. Helgrind:螺纹错误检测器

    目录 7.1.概观7.2.检测到的错误:POSIX pthread API的滥用7.3.检测到的错误:锁定排序不一致7.4.检测到的错误:数据竞赛 7.4.1.简单数据竞赛7.4.2.Helgrind ...

  4. Memcheck:一个内存错误检测器

    目录 4.1.概观4.2.来自Memcheck的错误消息说明 4.2.1.非法读取/非法写入错误4.2.2.使用未初始化的值4.2.3.在系统调用中使用未初始化或不可寻址的值4.2.4.非法释放4.2 ...

  5. 【linux】Valgrind工具集详解(七):Memcheck(内存错误检测器)

    一.概述 Memcheck是一个内存错误检测器.它可以检测C和C ++程序中常见的以下问题: 1.非法内存:如越界.释放后继续访问: 2.使用未初始化的值: 3.释放内存错误:如double-free ...

  6. C# 在异步中使用HttpWebRequest出现的“正在终止线程”错误的解决方案

    C# 在异步中使用HttpWebRequest出现的"正在终止线程"错误的解决方案 参考文章: (1)C# 在异步中使用HttpWebRequest出现的"正在终止线程& ...

  7. python捕捉线程错误_Pythonrequests多线程抓取出现HTTPConnectionPoolMaxretiresexceeded异常...

    问题: Python requests 多线程抓取 出现HTTPConnectionPool Max retires exceeded异常 描述: 主要代码如下:import threading im ...

  8. Response.Redirect 产生的“正在中止线程”错误

    这两天在开发调试过程中,老是会出现在一个 "正在中止线程"(ThreadAbortException)的例外信息. 例外是由 Response.Redirect 方法产生的,虽然知 ...

  9. Qt线程错误记录registered using qRegisterMetaType()

    1.问题 在多线程连接信号槽,发送QVector& 类型时抛出错误: C++类,有些时候要是调用Qt的信号槽当做参数进行跨线程发送,就会出现如下问题: 这种情况一般,编译可以通过,但会出现如下 ...

最新文章

  1. LTE - PUCCH Format2
  2. Jenkins 流水线 获取git 分支列表_jenkins的安装和配置 自动化部署 码云 gitee
  3. 13张图彻底搞懂分布式系统服务注册与发现原理
  4. R语言ggplot2可视化:为图像中的均值竖线、中位数竖线、 geom_vline添加图例(legend)
  5. c++ swap函数头文件_C++函数模板(泛型编程)
  6. Python读取.edf格式脑电数据文件
  7. autossh配置socks代理
  8. 论文浅尝 - ICLR2020 | Abductive Commonsense Reasoning
  9. android 引用非 android 工程,Unity3D调用android方法(非插件方式)
  10. c 表达式必须是可修改的左值_C++中的左值,右值,左值引用,右值引用
  11. np.random.RandomState、np.random.rand、np.random.random、np.random_sample
  12. python hook_五分钟内用Python实现GitHook
  13. 字符串属性和函数的使用
  14. 20181225面试
  15. 深度学习之Bias/Variance偏差、方差
  16. 计算机专业在线作图工具
  17. 软件项目管理存在的问题及改进措施
  18. yy直播接口php,api.php · yyboss/phpcms - Gitee.com
  19. 热浪寒浪统计在python上的实现
  20. 基于labview开发平台的声音信号采集及处理系统设计(任务书+lunwen+翻译及原文+vi源文件+查重报告)

热门文章

  1. ubuntu下ZED相机开发环境安装
  2. R统计绘图 - 热图简化
  3. ubuntu下安装latex
  4. js ajax java传参_js使用ajax传值给后台,后台返回字符串处理方法
  5. NOIP2015普及组第1题 45 金币 方法三(python3实现)
  6. 洛谷、牛客网、AcWing 刷题(python版)
  7. 【原型设计】第四节:Axure RP9 交面交互的使用说明 01 打开链接交互效果
  8. rpm 安装 忽略依赖_rpm 解决依赖的方法
  9. ThinkPHP6项目基操(17.实战部分 Filesystem文件上传)
  10. 基于java的银行ATM系统设计(含源文件)