为什么我的CRITICAL_SECTION的RecursionCount值过高?死锁问题排查
关于临界区字段的说明
你看到的RecursionCount = 176已经明确说明日志临界区没有被正确释放:Win32的CRITICAL_SECTION是可重入锁,该字段代表持有锁的线程的重入次数,每调用一次EnterCriticalSection就加1,每调用一次LeaveCriticalSection就减1,只有减到0的时候锁才会被真正释放,其他线程才能拿到。176的数值说明主线程已经重入获取了176次该锁,没有对应次数的释放操作,所以锁一直被主线程持有。
锁被主线程长期持有的常见原因
- 自定义跟踪处理函数递归调用Allegro日志接口:Allegro调用你注册的handler前已经持有了日志锁,如果你的handler内部又调用了
ALLEGRO_INFO/al_trace等Allegro日志API,就会触发递归拿锁,重入次数持续累加。如果handler内部存在提前返回、异常抛出等逻辑,就会导致后续的解锁逻辑无法执行,锁永远不释放。 - 解锁逻辑补加位置错误:Allegro默认的日志流程中,解锁逻辑原本位于默认日志输出分支的末尾,如果你只给默认分支补了解锁,自定义handler的分支还是没有对应解锁操作,就会出现注册handler后锁不释放的问题。
- handler内部触发了Allegro内部日志:即使你没有主动在handler里打Allegro日志,如果handler内部调用了Allegro的窗口操作、资源加载、事件处理等API,这些API的Debug/Info级别的内部日志也会触发递归拿锁,导致同样的问题。
操作错误排查方向
优先检查你的自定义跟踪处理函数实现:
- 确认handler没有调用任何Allegro提供的API
- 确认handler没有调用你自己实现的、内部会触发Allegro日志的工具函数
- 确认handler内部没有异常或者提前返回的逻辑跳过了后续的解锁流程
- 排查主线程是否存在持有其他锁的时候调用Allegro日志接口的情况,避免和Allegro子线程形成交叉死锁
具体调试方法
GDB侧操作
- 加条件断点跟踪锁操作:给
EnterCriticalSection和LeaveCriticalSection函数加断点,触发条件为操作的临界区地址等于&(trace_info.trace_mutex.cs),每次断点命中时记录当前线程ID和调用栈,统计主线程的加锁、解锁次数是否匹配,找出多余的加锁操作位置。 - 打印主线程完整调用栈:死锁发生时,执行
thread 0x4750切换到主线程,再执行bt full打印全量调用栈,确认主线程当前的执行位置,是否卡在自定义handler内部的某个调用上。 - 检查子线程调用栈:切换到卡在
ALLEGRO_INFO的子线程,打印调用栈确认该日志的触发时机,是否是在主线程持有锁期间被调用。
Allegro侧操作
- 测试极简handler:把你的自定义handler替换为仅写入本地文件的最简实现,不调用任何其他接口,测试死锁是否消失,快速定位是不是handler内部逻辑导致的问题。
- 修正trace逻辑的解锁流程:查看Allegro源码
src/debug.c中的al_trace实现,把解锁逻辑移到所有分支的公共退出路径上,不管有没有注册自定义handler、不管handler是否正常返回,都必须执行解锁操作。 - 调整注册时机:等
al_install_system初始化完成、所有内置线程启动完毕后,再调用al_register_trace_handler注册自定义处理函数,避免初始化阶段的线程竞争。
内容的提问来源于stack exchange,提问作者BugSquasher
相关产品推荐
相关产品推荐

