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
This commit is contained in:
light-city
2026-10-13 19:23:54 +08:00
parent cfc9af1fcb
commit dfe1c933b3
159 changed files with 20461 additions and 182 deletions

View File

@@ -0,0 +1,97 @@
> 来自 [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一键部署(这一期的视频,后面打算给大家录制一下),感兴趣的联系我即可。