QWorker更新-在Win32平台上枚举作业状态加上堆栈显示

在多线程编程中,死锁是一个绕不过去的话题,一旦发生死锁,要想知道程序死在那儿,为啥死的,是一个艰难的选择。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;

运行结果如下:

EnumWorkerStatus

我们可以看到,线程23当前正处于忙碌中,执行的作业是main单元的TForm1.DoLongTimeWork函数,其它的工作者处于空闲状态。

我们再结合EnumWaitChains函数,就可以很方便跟踪出死锁的状态。

【注意】

EnumWaitChain是调用Windows的API,所以受Windows版本限制,运行在Windows Vista或Windows Server 2008以上时,会支持该功能。

看下在的代码:

...
ShowMessage(EnumWaitChains);
...

运行效果:

WaitChains

看上面的结果中的线程7176的状态:

 线程 7176 <运行中>
 CriticalSection <等待中>
 线程 8988 <运行中>

这说明线程7176和线程8988都在使用同一个临界,那么我只需要再结合各个线程的堆栈信息,就可以定位到出错的行。

ShowMessage(
    '线程 '+IntToStr(AThread1)+' 堆栈'#13#10+StackByThreadId(AThread1)+
    '线程 ’+IntToStr(AThread2)+' 堆栈'#13#10+StackByThreadId(AThread2)
  );

看堆栈跟踪效果:

debugstacks

好吧,从上面看,我们锁冲突分别位于线程8644的第77行函数TForm4.Button3Click和线程7464的第System.SyncObjs.pas中的第1023行试图进入临界,这样一清二楚,然后接下来的事情就是去修改代码,保证它们不冲突就好了。

分享到: