Multithreading Delphi:通过报告锁上运行线程的调用堆栈来调试关键部分挂起;“失败”;

Multithreading Delphi:通过报告锁上运行线程的调用堆栈来调试关键部分挂起;“失败”;,multithreading,delphi,deadlock,critical-section,Multithreading,Delphi,Deadlock,Critical Section,我正在寻找一种调试罕见的Delphi7关键部分(TCriticalSection)挂起/死锁的方法。在本例中,如果一个线程在一个关键部分上等待的时间超过10秒,我想生成一个报告,其中包含当前锁定该关键部分的线程的堆栈跟踪,以及等待10秒后未能锁定该关键部分的线程的堆栈跟踪。如果引发异常或应用程序终止,则可以 如果可能的话,我更愿意继续使用关键部分,而不是使用其他同步原语,但可以在必要时切换(例如获取超时功能) 如果工具/方法在IDE之外的运行时工作,这是一个额外的好处,因为这很难按需复制。在极少

我正在寻找一种调试罕见的Delphi7关键部分(TCriticalSection)挂起/死锁的方法。在本例中,如果一个线程在一个关键部分上等待的时间超过10秒,我想生成一个报告,其中包含当前锁定该关键部分的线程的堆栈跟踪,以及等待10秒后未能锁定该关键部分的线程的堆栈跟踪。如果引发异常或应用程序终止,则可以

如果可能的话,我更愿意继续使用关键部分,而不是使用其他同步原语,但可以在必要时切换(例如获取超时功能)

如果工具/方法在IDE之外的运行时工作,这是一个额外的好处,因为这很难按需复制。在极少数情况下,我可以在IDE中复制死锁,如果我尝试暂停以开始调试,IDE就只是坐在那里不做任何事情,永远不会达到可以查看线程或调用堆栈的状态。不过,我可以重置正在运行的程序


更新:在这种情况下,我只处理一个关键部分和两个线程,所以这可能不是锁排序问题。我相信有一个不正确的嵌套尝试跨两个不同的线程进入锁,这会导致死锁。

如果您希望能够等待超时,可以尝试用TEvent信号替换关键部分。您可以说等待事件,给它一个超时长度,然后检查结果代码。如果设置了信号,则可以继续。否则,如果超时,则引发异常


至少,2010年我会这么做。我不确定Delphi7是否有TEvent,但它可能有。

您应该创建并使用自己的锁对象类。它可以使用关键部分或互斥体来实现,这取决于您是否要调试它

创建您自己的类还有一个额外的好处:您可以实现锁定层次结构,并在违反时引发异常。每次锁的顺序不完全相同时,就会发生死锁。为每个锁指定一个锁级别可以检查锁的顺序是否正确。您可以将当前锁级别存储在threadvar中,并且只允许使用具有较低锁级别的锁,否则会引发异常。这将捕获所有违规行为,即使在没有死锁发生的情况下也是如此,因此它将大大加快调试速度

至于获取线程的堆栈跟踪,这里有很多关于堆栈溢出的问题

更新

你写道:

在本例中,我只处理一个关键部分和两个线程,所以这可能不是锁排序问题。我认为有一种不正确的嵌套尝试跨两个不同的线程进入锁,这会导致死锁

这不可能是全部。在Windows上,没有办法单独使用两个线程和一个关键部分来死锁,因为关键部分可以由一个线程在那里递归获取。必须涉及另一种阻塞机制,例如
SendMessage()
调用


但是如果您真的只处理两个线程,那么其中一个线程必须是main/VCL/GUI线程。在这种情况下,您应该能够使用MadExcept功能。它将尝试向主线程发送一条消息,但在经过一段可自定义的时间后,消息未被处理,因此失败。如果您的主线程在关键部分阻塞,而另一个线程在消息处理调用上阻塞,那么MadExcept应该能够捕捉到这一点,并为两个线程提供堆栈跟踪。

这不是对您问题的直接回答,而是我最近遇到的一件事,它让我(和几位同事)感到困扰被难住了一会儿

这是一个断断续续的线程挂起,涉及到一个关键的部分,一旦我们知道了原因,这是非常明显的,给了我们所有人一个“哦”的时刻。然而,它确实需要一些认真的搜寻才能找到(添加越来越多的跟踪日志以精确定位令人不快的声明),这就是为什么我认为我应该提到它

它也在一个关键路段。另一个线程确实获得了那个关键部分。死锁本身似乎不是原因,因为只涉及一个关键部分,所以以不同的顺序获取锁不会有问题。持有临界段的线程应该继续,然后释放锁,允许另一个线程获得它

最后,持有锁的线程最终访问了(IIRC)组合框的ItemIndex,这看起来相当无害。不幸的是,获取ItemIndex依赖于消息处理。等待锁的线程是主应用程序线程。。。(以防有人怀疑:主线程执行所有消息处理…)

如果vcl从一开始就有点明显的参与,我们可能早就想到了这一点。然而,它是从非ui相关的代码开始的,vcl的参与只有在沿着调用树添加插装(进入-退出跟踪)并通过所有触发事件及其处理程序返回到ui代码之后才变得明显


只希望这个故事能对面临神秘悬念的人有所帮助。

使用互斥而不是关键部分。互斥体和临界区之间有一点区别——临界区更有效,而互斥体更灵活。您可以轻松地在互斥体和关键部分之间切换,例如使用调试版本中的互斥体

对于临界截面,我们使用:

var
  FLock: TRTLCriticalSection;

  InitializeCriticalSection(FLock);  // create lock
  DeleteCriticalSection(FLock);      // free lock
  EnterCriticalSection(FLock);       // acquire lock
  LeaveCriticalSection(FLock);       // release lock
与互斥体相同:

var FLock: THandle;

  FLock:= CreateMutex(nil, False, nil);  // create lock
  CloseHandle(FLock);                    // free lock
  WaitForSingleObject(FLock, Timeout);   // acquire lock
  ReleaseMutex(FLock);                   // release lock
通过实现acquire lock fu,可以对互斥对象使用超时(毫秒;10000表示10秒)
function AcquireLock(Lock: THandle; TimeOut: LongWord): Boolean;
begin
  Result:= WaitForSingleObject(Lock, Timeout) = WAIT_OBJECT_0;
end;
while not TryEnterCriticalSection(fLock) and (additional_checks) do
begin
  deal_with_failure();
  sleep(500); // wait 500 ms
end;