C 如何排查并发编程死锁问题?
最近在Apache arrow里面写一个支持并行的算子:nested loop join,然后既然涉及到并行,这里就会遇到大家常说的死锁问题,假设你碰到了死锁问题,如何调试与定位呢?
那这便是本篇目标,帮助你快速掌握并发编程:死锁检测与死锁调试问题,非常的干,好了进行正文。
1.引入
为了更好的讲解死锁,我们用一个程序来引入。
代码语言:javascript复制std::mutex gMutex;
int t2() {
std::lock_guard<std::mutex> m(gMutex);
return 0;
}
int t1() {
std::lock_guard<std::mutex> l(gMutex);
return t2();
}
相信看这个程序,大家都会觉得有问题,死锁了!问题出在t1()
函数和t2()
函数中都对全局的互斥锁gMutex
进行了加锁操作,但是t1()
函数在加锁后调用了t2()
函数,而t2()
函数内部又试图再次对gMutex
进行加锁。
t1锁已经加上了,但还没释放,t2又去加锁,两个人都在等待谁先释放,进入了死循环,实际在项目中代码并不会如这里这么简单,非常的复杂,例如:我在Apache arrow中写的代码是这样:
代码语言:javascript复制Status OnBuildSideFinished(size_t thread_index) {
std::lock_guard<std::mutex> guard(probe_side_mutex_);
// do something
accumulate_build_ready_ = true;
return scheduler_->StartTaskGroup(thread_index,task_group_probe_,queued_batches_to_probe_.batch_count());
}
你看这个代码比上面的场景就复杂多了,嵌套了至少5层堆栈,剩余代码还没贴出来,但是这两个本质都是一个死锁模型。
2.调试
讲解了死锁模型之后,碰到这种问题,如何定位呢?
这里可以采用两种办法,第一种直接运行程序,然后gdb上去。
例如:
代码语言:javascript复制./a.out
然后找到进程号后:
代码语言:javascript复制gdb -p xxx
此时我们可以得到及格正在等待的线程。
代码语言:javascript复制(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fe2740 (LWP 32301) "a.out" 0x00007ffff7bc8017 in pthread_join () from /lib64/libpthread.so.0
2 Thread 0x7ffff6fd0700 (LWP 32305) "a.out" 0x00007ffff7bcd54d in __lll_lock_wait () from /lib64/libpthread.so.0
然后去看__lll_lock_wait
的堆栈,例如这里我看了2号线程,然后查看堆栈得到t1与t2的行号,直接可以定位到哪里出了问题,非常的直观!
除了这种方式之外,还可以直接gdb上去运行程序,此时会卡死,然后ctrl c杀掉之后也是可以跟上面一样的内容。
例如:
代码语言:javascript复制(gdb) r
Starting program: /home/light/a.out
Missing separate debuginfos, use: debuginfo-install glibc-2.17-326.el7_9.x86_64
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
Hello World!
[New Thread 0x7ffff6fd0700 (LWP 32305)]
^C
Thread 1 "a.out" received signal SIGINT, Interrupt.
0x00007ffff7bc8017 in pthread_join () from /lib64/libpthread.so.0
Missing separate debuginfos, use: debuginfo-install libgcc-4.8.5-44.el7.x86_64 libstdc -4.8.5-44.el7.x86_64
(gdb) info threads
Id Target Id Frame
* 1 Thread 0x7ffff7fe2740 (LWP 32301) "a.out" 0x00007ffff7bc8017 in pthread_join () from /lib64/libpthread.so.0
2 Thread 0x7ffff6fd0700 (LWP 32305) "a.out" 0x00007ffff7bcd54d in __lll_lock_wait () from /lib64/libpthread.so.0
好了,本节就讲这么多,感兴趣的欢迎转发这篇硬核文章!