应用场景
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
是一个竞态资源,未加锁访问,存在访问冲突conflicts
。
解决方案
- 使用原子变量
- 加锁保护
此处采用原子变量的方式进行举例:
#include <thread>
#include <atomic>
using namespace std;
atomic_uint32_t g_num {0};
int main()
{
auto run = [&]() {
g_num++;
};
thread th(run);
g_num++;
th.join();
return 0;
}
输出日志结果:
==820== Helgrind, a thread error detector
==820== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==820== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==820== Command: ./test
==820==
==820==
==820== Use --history-level=approx or =none to gain increased speed, at
==820== the cost of reduced accuracy of conflicting-access information
==820== For lists of detected and suppressed errors, rerun with: -s
==820== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
多线程死锁问题
代码示例:
#include <vector>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
uint32_t g_num = 0;
mutex mtxA;
mutex mtxB;
void Fun1()
{
lock_guard<mutex> guard1(mtxA);
this_thread::sleep_for(chrono::milliseconds(10)); // 增加延时,是为了另外一个线程获取到另外一把锁
lock_guard<mutex> guard2(mtxB);
g_num++;
}
void Fun2()
{
lock_guard<mutex> guard2(mtxB);
this_thread::sleep_for(chrono::milliseconds(10));
lock_guard<mutex> guard1(mtxA);
g_num++;
}
int main()
{
vector<thread> ths;
ths.push_back(thread(Fun1));
ths.push_back(thread(Fun2));
for (auto& th : ths) {
th.join();
}
return 0;
}
注:该程序死锁了,需要使用ctrl+c
终止程序。
通过工具使用章节,运行后的结果如下:
==1999== Helgrind, a thread error detector
==1999== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==1999== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==1999== Command: ./test
==1999==
==1999==
==1999== Process terminating with default action of signal 2 (SIGINT)
==1999== at 0x486CCD7: __pthread_clockjoin_ex (pthread_join_common.c:145)
==1999== by 0x483FB15: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==1999== by 0x495D046: std::thread::join() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==1999== by 0x109624: main (in /tmp/valgrind/test)
==1999== ---Thread-Announcement------------------------------------------
==1999==
==1999== Thread #2 was created
==1999== at 0x4BA2122: clone (clone.S:71)
==1999== by 0x486A2EB: create_thread (createthread.c:101)
==1999== by 0x486BE0F: pthread_create@@GLIBC_2.2.5 (pthread_create.c:817)
==1999== by 0x4842917: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==1999== 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)
==1999== by 0x109F28: std::thread::thread<void (&)(), , void>(void (&)()) (in /tmp/valgrind/test)
==1999== by 0x109578: main (in /tmp/valgrind/test)
==1999==
==1999== ----------------------------------------------------------------
==1999==
==1999== Thread #2: Exiting thread still holds 1 lock
==1999== at 0x4876170: __lll_lock_wait (lowlevellock.c:52)
==1999== by 0x486E0A2: pthread_mutex_lock (pthread_mutex_lock.c:80)
==1999== by 0x483FE78: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==1999== by 0x10972D: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==1999== by 0x10989B: std::mutex::lock() (in /tmp/valgrind/test)
==1999== by 0x109A0F: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==1999== by 0x10940F: Fun1() (in /tmp/valgrind/test)
==1999== by 0x10B85F: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==1999== by 0x10B7F7: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==1999== by 0x10B789: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==1999== by 0x10B746: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==1999== by 0x10B717: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==1999==
==1999== ---Thread-Announcement------------------------------------------
==1999==
==1999== Thread #3 was created
==1999== at 0x4BA2122: clone (clone.S:71)
==1999== by 0x486A2EB: create_thread (createthread.c:101)
==1999== by 0x486BE0F: pthread_create@@GLIBC_2.2.5 (pthread_create.c:817)
==1999== by 0x4842917: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==1999== 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)
==1999== by 0x109F28: std::thread::thread<void (&)(), , void>(void (&)()) (in /tmp/valgrind/test)
==1999== by 0x1095AA: main (in /tmp/valgrind/test)
==1999==
==1999== ----------------------------------------------------------------
==1999==
==1999== Thread #3: Exiting thread still holds 1 lock
==1999== at 0x4876170: __lll_lock_wait (lowlevellock.c:52)
==1999== by 0x486E0A2: pthread_mutex_lock (pthread_mutex_lock.c:80)
==1999== by 0x483FE78: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==1999== by 0x10972D: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==1999== by 0x10989B: std::mutex::lock() (in /tmp/valgrind/test)
==1999== by 0x109A0F: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==1999== by 0x1094DA: Fun2() (in /tmp/valgrind/test)
==1999== by 0x10B85F: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==1999== by 0x10B7F7: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==1999== by 0x10B789: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==1999== by 0x10B746: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==1999== by 0x10B717: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==1999==
==1999==
==1999== Use --history-level=approx or =none to gain increased speed, at
==1999== the cost of reduced accuracy of conflicting-access information
==1999== For lists of detected and suppressed errors, rerun with: -s
==1999== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
从上面的打印输出日志可以看出:
一般程序退出时,都会解锁,但是在程序退出时,仍然持有锁,说明此处发生死锁了。
解决方案
多线程情况下的对多个互斥信号量的加锁顺序保持一致。
程序未死锁,但有死锁风险
将上诉代码改造一下,把所有的延时的调用都删除:
#include <vector>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
uint32_t g_num = 0;
mutex mtxA;
mutex mtxB;
void Fun1()
{
lock_guard<mutex> guard1(mtxA);
lock_guard<mutex> guard2(mtxB);
g_num++;
}
void Fun2()
{
lock_guard<mutex> guard2(mtxB);
lock_guard<mutex> guard1(mtxA);
g_num++;
}
int main()
{
vector<thread> ths;
ths.push_back(thread(Fun1));
ths.push_back(thread(Fun2));
for (auto& th : ths) {
th.join();
}
return 0;
}
编译后,再借助valgrind的helgrind工具,运行结果如下:
==137== Helgrind, a thread error detector
==137== Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
==137== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==137== Command: ./test
==137==
==137== ---Thread-Announcement------------------------------------------
==137==
==137== Thread #3 was created
==137== at 0x4BA2122: clone (clone.S:71)
==137== by 0x486A2EB: create_thread (createthread.c:101)
==137== by 0x486BE0F: pthread_create@@GLIBC_2.2.5 (pthread_create.c:817)
==137== by 0x4842917: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== 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)
==137== by 0x109A94: std::thread::thread<void (&)(), , void>(void (&)()) (in /tmp/valgrind/test)
==137== by 0x10951E: main (in /tmp/valgrind/test)
==137==
==137== ----------------------------------------------------------------
==137==
==137== Thread #3: lock order "0x10F060 before 0x10F0A0" violated
==137==
==137== Observed (incorrect) order is: acquisition of lock at 0x10F0A0
==137== at 0x483FEDF: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== by 0x10965A: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==137== by 0x1097C9: std::mutex::lock() (in /tmp/valgrind/test)
==137== by 0x109905: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==137== by 0x10943B: Fun2() (in /tmp/valgrind/test)
==137== by 0x10B28B: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B223: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B1B5: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==137== by 0x10B172: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==137== by 0x10B143: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==137== by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==137== by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137==
==137== followed by a later acquisition of lock at 0x10F060
==137== at 0x483FEDF: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== by 0x10965A: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==137== by 0x1097C9: std::mutex::lock() (in /tmp/valgrind/test)
==137== by 0x109905: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==137== by 0x10944E: Fun2() (in /tmp/valgrind/test)
==137== by 0x10B28B: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B223: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B1B5: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==137== by 0x10B172: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==137== by 0x10B143: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==137== by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==137== by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137==
==137== Required order was established by acquisition of lock at 0x10F060
==137== at 0x483FEDF: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== by 0x10965A: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==137== by 0x1097C9: std::mutex::lock() (in /tmp/valgrind/test)
==137== by 0x109905: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==137== by 0x109396: Fun1() (in /tmp/valgrind/test)
==137== by 0x10B28B: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B223: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B1B5: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==137== by 0x10B172: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==137== by 0x10B143: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==137== by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==137== by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137==
==137== followed by a later acquisition of lock at 0x10F0A0
==137== at 0x483FEDF: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== by 0x10965A: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==137== by 0x1097C9: std::mutex::lock() (in /tmp/valgrind/test)
==137== by 0x109905: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==137== by 0x1093A9: Fun1() (in /tmp/valgrind/test)
==137== by 0x10B28B: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B223: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B1B5: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==137== by 0x10B172: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==137== by 0x10B143: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==137== by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==137== by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137==
==137== Lock at 0x10F060 was first observed
==137== at 0x483FEDF: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== by 0x10965A: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==137== by 0x1097C9: std::mutex::lock() (in /tmp/valgrind/test)
==137== by 0x109905: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==137== by 0x109396: Fun1() (in /tmp/valgrind/test)
==137== by 0x10B28B: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B223: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B1B5: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==137== by 0x10B172: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==137== by 0x10B143: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==137== by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==137== by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== Address 0x10f060 is 0 bytes inside data symbol "mtxA"
==137==
==137== Lock at 0x10F0A0 was first observed
==137== at 0x483FEDF: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== by 0x10965A: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==137== by 0x1097C9: std::mutex::lock() (in /tmp/valgrind/test)
==137== by 0x109905: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==137== by 0x1093A9: Fun1() (in /tmp/valgrind/test)
==137== by 0x10B28B: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B223: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==137== by 0x10B1B5: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==137== by 0x10B172: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==137== by 0x10B143: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==137== by 0x495CDE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==137== by 0x4842B1A: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_helgrind-amd64-linux.so)
==137== Address 0x10f0a0 is 0 bytes inside data symbol "mtxB"
==137==
==137==
==137==
==137== Use --history-level=approx or =none to gain increased speed, at
==137== the cost of reduced accuracy of conflicting-access information
==137== For lists of detected and suppressed errors, rerun with: -s
==137== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 11 from 7)
从上诉日志中,请关注以下这句话:
Thread #3: lock order "0x10F060 before 0x10F0A0" violated
就是说,线程3违反了锁的顺序。从日志中,也可以找到相应的锁的签名,看到其对应的关系和顺序。在平常工作生活中,可以借助检测结果,可以快速的发现,可能存在死锁风险问题。
锁范围问题
只能检测某一个锁占用的时间,不能统计多处、多个锁的占用时间。应用意义不是很大。
代码示例:
#include <vector>
#include <thread>
#include <chrono>
#include <mutex>
using namespace std;
uint32_t g_num = 0;
mutex mtxA;
void Fun1()
{
lock_guard<mutex> guard1(mtxA);
this_thread::sleep_for(chrono::milliseconds(20));
g_num++;
}
void Fun2()
{
lock_guard<mutex> guard1(mtxA);
this_thread::sleep_for(chrono::milliseconds(50));
g_num++;
}
int main()
{
vector<thread> ths;
ths.push_back(thread(Fun1));
ths.push_back(thread(Fun2));
for (auto& th : ths) {
th.join();
}
return 0;
}
通过工具使用章节,运行后的结果如下:
==473== drd, a thread error detector
==473== Copyright (C) 2006-2017, and GNU GPL'd, by Bart Van Assche.
==473== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==473== Command: ./test
==473==
==473== Thread 2:
==473== Acquired at:
==473== at 0x484582C: pthread_mutex_lock (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_drd-amd64-linux.so)
==473== by 0x1096EF: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==473== by 0x10985D: std::mutex::lock() (in /tmp/valgrind/test)
==473== by 0x1099D1: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==473== by 0x1093D6: Fun1() (in /tmp/valgrind/test)
==473== by 0x10B821: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==473== by 0x10B7B9: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==473== by 0x10B74B: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==473== by 0x10B708: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==473== by 0x10B6D9: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==473== by 0x4968DE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==473== by 0x48424BA: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_drd-amd64-linux.so)
==473== Lock on mutex 0x10f060 was held during 22 ms (threshold: 10 ms).
==473== at 0x484687C: pthread_mutex_unlock (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_drd-amd64-linux.so)
==473== by 0x10971E: __gthread_mutex_unlock(pthread_mutex_t*) (in /tmp/valgrind/test)
==473== by 0x10988F: std::mutex::unlock() (in /tmp/valgrind/test)
==473== by 0x1099F4: std::lock_guard<std::mutex>::~lock_guard() (in /tmp/valgrind/test)
==473== by 0x109417: Fun1() (in /tmp/valgrind/test)
==473== by 0x10B821: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==473== by 0x10B7B9: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==473== by 0x10B74B: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==473== by 0x10B708: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==473== by 0x10B6D9: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==473== by 0x4968DE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==473== by 0x48424BA: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_drd-amd64-linux.so)
==473== mutex 0x10f060 was first observed at:
==473== at 0x484576C: pthread_mutex_lock (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_drd-amd64-linux.so)
==473== by 0x1096EF: __gthread_mutex_lock(pthread_mutex_t*) (in /tmp/valgrind/test)
==473== by 0x10985D: std::mutex::lock() (in /tmp/valgrind/test)
==473== by 0x1099D1: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (in /tmp/valgrind/test)
==473== by 0x1093D6: Fun1() (in /tmp/valgrind/test)
==473== by 0x10B821: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (in /tmp/valgrind/test)
==473== by 0x10B7B9: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (in /tmp/valgrind/test)
==473== by 0x10B74B: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (in /tmp/valgrind/test)
==473== by 0x10B708: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (in /tmp/valgrind/test)
==473== by 0x10B6D9: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (in /tmp/valgrind/test)
==473== by 0x4968DE3: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28)
==473== by 0x48424BA: ??? (in /usr/lib/x86_64-linux-gnu/valgrind/vgpreload_drd-amd64-linux.so)
==473==
==473==
==473== For lists of detected and suppressed errors, rerun with: -s
==473== ERROR SUMMARY: 2 errors from 1 contexts (suppressed: 20 from 12)