You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为什么我的CRITICAL_SECTION的RecursionCount值过高?死锁问题排查

关于临界区字段的说明

你看到的RecursionCount = 176已经明确说明日志临界区没有被正确释放:Win32的CRITICAL_SECTION是可重入锁,该字段代表持有锁的线程的重入次数,每调用一次EnterCriticalSection就加1,每调用一次LeaveCriticalSection就减1,只有减到0的时候锁才会被真正释放,其他线程才能拿到。176的数值说明主线程已经重入获取了176次该锁,没有对应次数的释放操作,所以锁一直被主线程持有。

锁被主线程长期持有的常见原因

  1. 自定义跟踪处理函数递归调用Allegro日志接口:Allegro调用你注册的handler前已经持有了日志锁,如果你的handler内部又调用了ALLEGRO_INFO/al_trace等Allegro日志API,就会触发递归拿锁,重入次数持续累加。如果handler内部存在提前返回、异常抛出等逻辑,就会导致后续的解锁逻辑无法执行,锁永远不释放。
  2. 解锁逻辑补加位置错误:Allegro默认的日志流程中,解锁逻辑原本位于默认日志输出分支的末尾,如果你只给默认分支补了解锁,自定义handler的分支还是没有对应解锁操作,就会出现注册handler后锁不释放的问题。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.21 16:54:07