应用场景
C++多线程运行的程序中:
- 接口使用问题:常见的多线程接口使用不当问题(不是本文介绍重点)
- 竞态问题:多线程场景下未加锁保护竞态资源
- 多线程死锁问题
- 锁范围问题
工具使用局限性
- 只能检测基于
POSIX
的pthread
库实现的多线程程序。当前C++11的thread
库在Linux就是使用POSIX
的pthread
库实现的。 - 只能检测程序运行过的流程中的多线程竞态资源访问、死锁、加锁范围过大等问题。因此,需要覆盖完全的话,则需要覆盖所有的分支和流程。
- 仅支持程序的正常退出和信号为
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
,表示互斥锁或者写着(独占),占用此资源超过10
ms,将报告信息。因此,通过查看每个锁占用的时间,根据自己的业务逻辑需要进行判定是否可以减小锁的范围,减小独占时间,提高并发性。
常见场景举例
竞态问题
代码示例:
#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
是一个竞态资源,未加锁访问,存在