set scheduler-locking on 是控制并发度最直接有效的手段,启用后仅当前线程响应next/step/continue等命令,其余线程被冻结,实现串行化调试,避免其他线程干扰共享变量或导致断点命中混乱。

set scheduler-locking on 是控制并发度最直接有效的手段
GDB 本身不提供“限制线程数”或“降低并发度”的开关,所谓“控制并发度”,实际是指**阻止其他线程在你单步或继续时偷偷运行**——否则你刚 next 一行,另一个线程就已执行完、退出、甚至改坏了共享变量。根本解法就是启用调度锁定:set scheduler-locking on。
启用后,只有当前线程能响应 next、step、continue 等命令;其余线程被冻结,状态保持在暂停那一刻。这不是模拟低并发,而是让调试行为变成“串行化观察”,避免干扰源。
注意三点:
-
set scheduler-locking on必须在程序启动后、首次run前或刚中断时立即设置,晚了可能已有线程跑飞 - 它不改变程序本身的线程创建逻辑(
std::thread该起多少个还是多少个),只约束 GDB 的执行调度权 - 若需临时放开(比如想看另一线程是否卡死),可用
set scheduler-locking off,但之后必须立刻thread 2切回目标线程再操作,否则焦点易丢失
thread apply all + condition 是间接压制并发干扰的实操方式
当你要验证某段临界区是否真被多个线程同时进入,又不想手动切来切去,可以用 thread apply all 批量下发命令并配合条件过滤,本质是“用指令代替人眼盯屏”。
立即学习“C++免费学习笔记(深入)”;
例如,在 pthread_mutex_lock 调用前加断点,并只让 ID=2 的线程停:
break pthread_mutex_lock thread 2
或者更稳妥地,结合线程名(需代码中调用 pthread_setname_np):
break counter::add_one if $_thread == 2
这样其他线程路过同一行也不会中断,等于人为把“并发触发”降为“单线程触发”。适用于复现竞态条件时隔离变量修改路径。
常见误操作:
- 在
printf或std::cout行设无条件断点 → 所有线程反复触发,调试流完全失控 - 用
thread apply all next试图“同步推进” → 各线程执行速度不同,栈帧错位,bt结果不可信 - 依赖
info threads输出顺序判断“哪个线程先跑” → 输出是快照,非实时调度视图
non-stop 模式不是用来控并发度,而是用来观察并发行为的
很多人误以为 set non-stop on 能“更好地管理多线程”,结果调试更混乱。它的真实用途只有一个:**允许你在某个线程停住时,仍能查看、操作其他正在运行的线程**。
但它会让所有线程独立运行,意味着:
- 你
continue当前线程,别的线程也跟着跑,无法冻结 -
info threads列表每秒刷新多次,*标记的“当前线程”可能下一秒就变了 - 必须搭配
thread apply all bt实时抓栈,不能只看单一线程的bt
所以,除非你明确要验证“两个线程是否真的同时卡在同一个锁上”,否则别开 non-stop。默认 non-stop off 才是稳定调试的基础模式。
编译和符号完整性决定你能控到什么粒度
再好的 GDB 命令,也救不了缺失调试信息的二进制。如果你发现 thread 2 切过去后显示 ??、backtrace 断在 ???、或者 $_thread 不可用,大概率是编译环节出了问题。
必须确保:
- 编译时带
-g(嵌入调试符号)且-O0(禁用优化),否则变量被优化掉、内联函数消失、行号错位 - C++11 及以上线程名支持需链接
-lpthread,且运行时调用pthread_setname_np才能让$_thread或$rdi等寄存器条件生效 - GDB 版本 ≥ 10.2 才支持
$_thread这类内置线程变量;旧版本只能靠寄存器或内存地址硬匹配
最容易被忽略的是:线程创建后立即进入密集循环(如 while(true) { cnt.add_one(); sleep(1ms); }),此时哪怕开了 set scheduler-locking on,你也很难在第一次迭代就命中断点——因为从 std::thread 构造到真正执行 add_one 之间有调度延迟。解决办法是在线程函数入口处设断点,或用 tbreak(临时断点)抢第一帧。


















