在多线程编程中,死锁是一个绕不过去的话题,一旦发生死锁,要想知道程序死在那儿,为啥死的,是一个艰难的选择。QWorker为用户新提供的EnumWorkerStatus函数,可以轻松的报告每个工作者的状态、正在处理的作业、及工作者处理作业的堆栈调用情况,通过跟踪这些信息,大家可以方便的抓取到每个工作者当前在干什么,从而可以确定出系统中的作业是那个线程相互阻塞造成问题发生。
要使用本功能,必需让编译器生成Map文件,以便程序知道指定的地址对应的是啥函数及源码信息。关于这方面的信息,请参考文章:Delphi/C++ Builder Map文件格式解析。
在程序运行过程中,你可以随时调用EnumWorkerStatus来枚举每个工作者的状态。当指定的工作者不空闲时,就会列出相应的工作者的调用堆栈,如下代码:
procedure TForm1.Button32Click(Sender: TObject); var AStatus: TQWorkerStatus; I: Integer; S: String; begin AStatus := Workers.EnumWorkerStatus; for I := Low(AStatus) to High(AStatus) do begin S := S + '【工作者 ' + IntToStr(I) + '(ID=' + IntToStr(AStatus[I].ThreadId) + ')】'#13#10 + ' 已处理:' + IntToStr(AStatus[I].Processed) + ' 状态:'; if AStatus[I].IsIdle then S := S + '空闲'#13#10#13#10 else begin S := S + '忙碌(作业:' + AStatus[I].ActiveJob + ')'#13#10; if Length(AStatus[I].Stacks) > 0 then S := S + ' 堆栈:'#13#10 + AStatus[I].Stacks + #13#10#13#10 else S := S + #13#10; end; end; ShowMessage(S); end;
运行结果如下:
我们可以看到,线程23当前正处于忙碌中,执行的作业是main单元的TForm1.DoLongTimeWork函数,其它的工作者处于空闲状态。
我们再结合EnumWaitChains函数,就可以很方便跟踪出死锁的状态。
【注意】
EnumWaitChain是调用Windows的API,所以受Windows版本限制,运行在Windows Vista或Windows Server 2008以上时,会支持该功能。
看下在的代码:
... ShowMessage(EnumWaitChains); ...
运行效果:
看上面的结果中的线程7176的状态:
线程 7176 <运行中> CriticalSection <等待中> 线程 8988 <运行中>
这说明线程7176和线程8988都在使用同一个临界,那么我只需要再结合各个线程的堆栈信息,就可以定位到出错的行。
ShowMessage( '线程 '+IntToStr(AThread1)+' 堆栈'#13#10+StackByThreadId(AThread1)+ '线程 ’+IntToStr(AThread2)+' 堆栈'#13#10+StackByThreadId(AThread2) );
看堆栈跟踪效果:
好吧,从上面看,我们锁冲突分别位于线程8644的第77行函数TForm4.Button3Click和线程7464的第System.SyncObjs.pas中的第1023行试图进入临界,这样一清二楚,然后接下来的事情就是去修改代码,保证它们不冲突就好了。