GDB 多线程调试踩坑记录:call 函数时提示 stopped in another thread 与 boost::unordered::concurrent_node_map
在 C++ 多线程开发中,使用 GDB 调试可以说是家常便饭。为了快速查看某个复杂对象的状态,我们经常会在断点处使用 call 或 p(print)命令去主动调用类内部的成员函数。
但这在并发场景下往往会暗藏杀机。今天在调试一个使用了 boost::unordered::concurrent_node_map 的核心数据流转模块时,就碰到了一个极其经典的 GDB 多线程报错,在此记录下彻底的解决方案。
1. 案发现场:一次失败的 call 调用
今天在调试一个数据同步模块(TopicDataManager)时,该模块底层的核心数据缓冲区(Buffer)使用的是 Boost 库提供的高性能并发容器 boost::unordered::concurrent_node_map。
由于这种并发容器的内部结构极其复杂,直接用 GDB 打印变量几乎没法看,所以我封装了一个 outputDataBufferInnerData() 专门用来将其内容格式化输出。程序停在断点后,我习惯性地在 GDB 中敲下命令:
(gdb) call outputDataBufferInnerData()
然而 GDB 并没有返回我期待的数据,而是无情地抛出了以下一连串警告,并强行中断了我的调用:
[Switching to Thread 0x7fffef7fe700 (LWP 67095)]
The program stopped in another thread while making a function call from GDB.
Evaluation of the expression containing the function
(TopicDataManager<KafkaFrame, SampleData, 0>::outputDataBufferInnerData() const) will be abandoned.
When the function is done executing, GDB will silently stop.
[Switching to thread 16 (Thread 0x7fffef7fe700 (LWP 67095))]
#0 TopicDataManager<KafkaFrame, SampleData, 0>::getDataFromDataBuffer<long, std::ratio<1l, 1000l> > (this=0x91b760, timestamp=1779640268440ms [2026-05-24 16:31:08], funSleepInterval=10000ms) at /tmp/tmp.TCyemxDEKr/src/AlgorithmModule/TopicDataManager.h:672
672 std::cout << "Waiting for data ..." << " Timestamp : " << timestamp.time_since_epoch().count() << std::endl;
2. 根因分析:GDB 的底层调度逻辑
这段报错的核心信息是:“The program stopped in another thread while making a function call from GDB.”(当 GDB 正在调用函数时,程序在另一个线程停住了)。
为什么会这样?这就涉及到了 GDB 默认的线程调度机制:
当我们在 GDB 里输入 call 强行执行一段代码时,GDB 为了让这段代码跑起来,会默认放开对整个进程的执行限制。这意味着不仅你当前的线程在跑,程序里的所有其他后台线程也会同时恢复运行。
在上面的日志中,当我调用 outputDataBufferInnerData() 时,后台的 16 号线程(负责拉取数据的线程)正好跑到了 TopicDataManager.h:672 行,由于那行代码附近可能触发了断点,或者发生了中断信号,GDB 的异常捕获机制介入,紧急叫停了整个程序,并无情地“抛弃”(abandoned)了我们刚才发起的函数调用。
3. 终极解法:冻结其他线程 (scheduler-locking)
要解决这个问题,我们需要人为干预 GDB 的线程调度器。告诉 GDB:“在我执行这个函数期间,把其他所有线程都冻结锁死,只允许我当前的线程偷偷跑一下。”
在 GDB 中,通过设置 scheduler-locking 即可完美解决。具体的执行步骤如下:
# 1. 开启线程调度锁(冻结其他所有后台线程)
(gdb) set scheduler-locking on
# 2. 重新调用你的目标函数,此时其他线程被冻结,绝对不会跳出来捣乱
(gdb) call outputDataBufferInnerData()
# 3. !!极其重要!!调用结束后,务必将调度锁关掉
(gdb) set scheduler-locking off
⚠️ 注意事项:执行完调用的检查后,一定要记得把 scheduler-locking 设回 off(或者设为 step)。如果忘记关掉它就直接输入 continue(c),那么整个程序将只有这一个线程在跑,其他所有后台的数据处理线程都会处于假死状态。
4. 进阶避坑:并发容器带来的“死锁幽灵”
虽然 set scheduler-locking on 是解决该问题的银弹,但在我们今天这个使用了 boost::unordered::concurrent_node_map 的场景下,它可能会引发极其可怕的死锁。
场景还原:
concurrent_node_map 是一个支持高并发读写的容器,内部充斥着各种细粒度的锁(如分段锁/桶锁等)。当你执行 scheduler-locking on 强行冻结所有其他线程时,极大概率有某个后台写入线程刚好在操作这个 Map,并且正持有它的某个内部锁。
此时,如果你的 outputDataBufferInnerData() 去遍历这个 Map,触发了对同一个桶的读写锁竞争,因为占有锁的那个线程已经被你“冻结”了,它永远无法释放锁。你当前的调用就会卡死在等待锁的过程中——死锁诞生了。
这个时候,整个 GDB 界面会完全失去响应(挂起),你只能通过按 Ctrl+C 强行中断。
总结与建议:
- 多线程下使用
call被打断时,使用set scheduler-locking on。 - 尽量避免在 GDB 中主动 call 带有锁竞争的逻辑。
- 对于
boost::unordered::concurrent_node_map这类自身带锁的并发容器,调试时如果要 Dump 其数据,一定要确认后台没有高频的并发写入,或者在设计之初就考虑好提供无锁的只读镜像机制供 Debug 使用。

浙公网安备 33010602011771号