C++线程异常检测工具(Valrind之helgrind和DRD)

应用场景

C++多线程运行的程序中:

  • 接口使用问题:常见的多线程接口使用不当问题(不是本文介绍重点)
  • 竞态问题:多线程场景下未加锁保护竞态资源
  • 多线程死锁问题
  • 锁范围问题

工具使用局限性

  • 只能检测基于POSIXpthread库实现的多线程程序。当前C++11的thread库在Linux就是使用POSIXpthread库实现的。
  • 只能检测程序运行过的流程中的多线程竞态资源访问、死锁、加锁范围过大等问题。因此,需要覆盖完全的话,则需要覆盖所有的分支和流程。
  • 仅支持程序的正常退出和信号为SIGINT(ctrl+c)的程序退出,不支持kill -9程序的操作

工具使用

首先将应用程序,编译多线程场景下的可执行程序,需要链接pthread库。

g++ -O0 xxxx.cpp -o xxxx -lpthread

helgrind工具

此工具可支持检测:多线程接口使用问题、竞态问题、死锁问题。

valgrind --tool=helgrind ./xxxx

DRD工具

此工具可支持检测:多线程接口使用问题、竞态问题、锁范围问题。

valgrind --tool=drd --exclusive-threshold=10 ./xxx

其中,--exclusive-threshold=10,表示互斥锁或者写着(独占),占用此资源超过10ms,将报告信息。因此,通过查看每个锁占用的时间,根据自己的业务逻辑需要进行判定是否可以减小锁的范围,减小独占时间,提高并发性。

常见场景举例

竞态问题

代码示例:

#include <thread>
using namespace std;

uint32_t g_num = 0;

int main()
{
    auto run = [&]() {
        g_num++;
    };

    thread th(run);
    g_num++;

    th.join();

    return 0;
}

通过工具使用章节,运行后的结果如下:

==721== Helgrind, a thread error detector
==721== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==721== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==721== Command: ./test
==721== 
==721== ---Thread-Announcement------------------------------------------
==721== 
==721== Thread #1 is the program's root thread
==721== 
==721== ---Thread-Announcement------------------------------------------
==721== 
==721== Thread #2 was created
==721==    at 0x4BA2122: clone (clone.S:71)
==721==    by 0x486A2EB: create_thread (createthread.c:101)
==721==    by 0x486BE0F: pthread_create@@GLIBC_2.2.5 (pthread_create.c:817)
==721==    by 0x4842917: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==721==    by 0x495D0A8: std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==721==    by 0x10935A: std::thread::thread<main::{lambda()#1}&, , void>(main::{lambda()#1}&) (in /tmp/valgrind/test)
==721==    by 0x109272: main (in /tmp/valgrind/test)
==721== 
==721== ----------------------------------------------------------------
==721== 
==721== Possible data race during read of size 4 at 0x10C01C by thread #1
==721== Locks held: none
==721==    at 0x109273: main (in /tmp/valgrind/test)
==721== 
==721== This conflicts with a previous write of size 4 by thread #2
==721== Locks held: none
==721==    at 0x10923B: main::{lambda()#1}::operator()() const (in /tmp/valgrind/test)
==721==    by 0x1097A6: void std::__invoke_impl<void, main::{lambda()#1}>(std::__invoke_other, main::{lambda()#1}&&) (in /tmp/valgrind/test)
==721==    by 0x109747: std::__invoke_result<main::{lambda()#1}>::type std::__invoke<main::{lambda()#1}>(std::__invoke_result&&, (main::{lambda()#1}&&)...) (in /tmp/valgrind/test)
==721==    by 0x1096E5: void std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==721==    by 0x1096A6: std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::operator()() (in /tmp/valgrind/test)
==721==    by 0x10967B: std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda()#1}> > >::_M_run() (in /tmp/valgrind/test)
==721==    by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==721==    by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==721==  Address 0x10c01c is 0 bytes inside data symbol "g_num"
==721== 
==721== ----------------------------------------------------------------
==721== 
==721== Possible data race during write of size 4 at 0x10C01C by thread #1
==721== Locks held: none
==721==    at 0x10927C: main (in /tmp/valgrind/test)
==721== 
==721== This conflicts with a previous write of size 4 by thread #2
==721== Locks held: none
==721==    at 0x10923B: main::{lambda()#1}::operator()() const (in /tmp/valgrind/test)
==721==    by 0x1097A6: void std::__invoke_impl<void, main::{lambda()#1}>(std::__invoke_other, main::{lambda()#1}&&) (in /tmp/valgrind/test)
==721==    by 0x109747: std::__invoke_result<main::{lambda()#1}>::type std::__invoke<main::{lambda()#1}>(std::__invoke_result&&, (main::{lambda()#1}&&)...) (in /tmp/valgrind/test)
==721==    by 0x1096E5: void std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==721==    by 0x1096A6: std::thread::_Invoker<std::tuple<main::{lambda()#1}> >::operator()() (in /tmp/valgrind/test)
==721==    by 0x10967B: std::thread::_State_impl<std::thread::_Invoker<std::tuple<main::{lambda()#1}> > >::_M_run() (in /tmp/valgrind/test)
==721==    by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==721==    by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==721==  Address 0x10c01c is 0 bytes inside data symbol "g_num"
==721== 
==721== 
==721== Use --history-level=approx or =none to gain increased speed, at
==721== the cost of reduced accuracy of conflicting-access information
==721== For lists of detected and suppressed errors, rerun with: -s
==721== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

从上面的日志可以看出,g_num是一个竞态资源,未加锁访问,存在

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值