windbg 分析线程死锁
一、第一步:宏观看整体指标 !tp
查看线程池Worker总数、Running/Idle、排队队列;
特征:Running=总线程、Idle=0、队列积压几千 → 大批量线程永久卡死不释放。
初步结论:不是瞬时并发高,是线程泄漏卡死。
回到顶部
二、第二步:排查托管锁 !syncblk
看每条:MonitorHeld=占用数、有无等待的Waiter线程
① 有大量等待线程 = 多lock互相争抢,传统Monitor死锁
② 只有占用、无等待线程 = 线程拿着锁卡在业务代码,锁是副产品,不是元凶(你的场景)
回到顶部
三、第三步:抽卡死线程栈(从syncblk里提取OS-TID)
~~[0xTID]s;!clrstack 打印单个卡死线程托管堆栈:
栈出现:ManualResetEventSlim.Wait → Task.InternalWait → Task.Wait()
→ 锁定:同步.Wait()阻塞异步任务,上下文死锁。
向上追溯业务类:定位出事方法(ServiceDiscovery、MatrixHelper、HttpModule、Controller)。
回到顶部
四、第四步:结合代码解释「偶发正常、随机卡死」
命中缓存/连接复用:GetAsync同步执行完毕,返回已完成Task,await不放线程,全链路顺行,正常释放;
真实跨网IO:返回未完成Task,await释放工作线程;IO完成回调要切回AspNetSynchronizationContext,但主线程被.Wait()占死 → 闭环死锁、线程永久滞留。
回到顶部
五、第五步:区分两种死锁、落地修复
传统lock死锁:A拿锁1等锁2、B拿锁2等锁1 → 改锁顺序、缩小lock范围;
ASP上下文死锁(本次故障):同步.Wait()/Result + await无ConfigureAwait(false)
修复二选一:
方案1:全链路改成async/await,删除所有.Wait();
方案2:所有await xxx.ConfigureAwait(false),切断回调绑定请求上下文。
更多推荐
所有评论(0)