Files
CPlusPlusThings/site_extras/cpp-concurrency-cpp-deadlock-debugging.md
light-city dfe1c933b3 docs: address community issue feedback (#5 #338 #142 #246 #136 #303)
- const README: rewrite the space-saving claim per issue #5 follow-ups
  (constant-expression const vs #define immediates; which const qualify
  as constant expressions)
- const README: fix the class-section diagnosis per issue #338 (overload
  resolution picks the const overload; the restriction only surfaces
  once that overload is removed)
- sizeof README: virtual-inheritance result is compiler-dependent
  (GCC merges vbptr+vfptr -> 16, MSVC keeps them separate -> 32) #246
- static README: new section on internal linkage / access restriction
  with anonymous-namespace comparison #142
- 10-day practice: full problem list table + fixed day links #136
- new design_pattern/singleton/singleton_default.hpp: reusable
  thread-safe CRTP singleton template, compile+concurrency tested #303
2026-10-13 19:23:54 +08:00

97 lines
3.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
> 来自 [C++那些事网站](https://light-city.github.io/stories_things/cpp-concurrency/cpp-deadlock-debugging/) 的文章,由站点同步脚本导出。
# C++ 死锁排查
## C++如何排查并发编程死锁问题?
最近在Apache arrow里面写一个支持并行的算子:nested loop join,然后既然涉及到并行,这里就会遇到大家常说的死锁问题,假设你碰到了死锁问题,如何调试与定位呢?
那这便是本篇目标,帮助你快速掌握并发编程:死锁检测与死锁调试问题,非常的干,好了进行正文。
## 1.引入
为了更好的讲解死锁,我们用一个程序来引入。
```cpp
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中写的代码是这样:
```cpp
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上去。
例如:
```
./a.out
```
然后找到进程号后:
```
gdb -p xxx
```
此时我们可以得到及格正在等待的线程。
```cpp
(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杀掉之后也是可以跟上面一样的内容。
例如:
```
(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
```
好了,本节就讲这么多,感兴趣的欢迎转发这篇硬核文章!于此同时,推荐一下自己的**线程池项目**,个人用视频讲解了这个项目,同时配上docker一键部署(这一期的视频,后面打算给大家录制一下),感兴趣的联系我即可。