Multithreading 应用程序挂起在SysUtils中->;DoneMonitor出口时的支持

Multithreading 应用程序挂起在SysUtils中->;DoneMonitor出口时的支持,multithreading,delphi,delphi-xe2,Multithreading,Delphi,Delphi Xe2,我正在编写一个线程非常密集的应用程序,它在退出时挂起 我追踪了系统的各个单元,找到了程序进入无限循环的地方。它位于SysUtils行19868->DoneMonitorSupport->CleanEventList: repeat until InterlockedCompareExchange(EventCache[I].Lock, 1, 0) = 0; procedure CleanEventList(var EventCache: array of TSyncEventItem);

我正在编写一个线程非常密集的应用程序,它在退出时挂起

我追踪了系统的各个单元,找到了程序进入无限循环的地方。它位于SysUtils行19868->DoneMonitorSupport->CleanEventList

repeat until InterlockedCompareExchange(EventCache[I].Lock, 1, 0) = 0;
  procedure CleanEventList(var EventCache: array of TSyncEventItem);
  var
    I: Integer;
  begin
    for I := Low(EventCache) to High(EventCache) do
    begin
      if InterlockedCompareExchange(EventCache[I].Lock, 1, 0) = 0 then
         DeleteSyncWaitObj(EventCache[I].Event);
      //repeat until InterlockedCompareExchange(EventCache[I].Lock, 1, 0) = 0;
      //DeleteSyncWaitObj(EventCache[I].Event);
    end;
  end;
我在网上搜索了一个解决方案,找到了两份QC报告:

不幸的是,这些似乎与我的情况无关,因为我既不使用TThreadList也不使用TMonitor

我非常确定我的所有线程都已完成并被销毁,因为它们都继承自保持创建/销毁计数的基本线程


以前有没有人遇到过类似的行为?你知道发现根本原因的任何策略吗?

我可以用Cosmin提供的例子重现你的问题。我也可以通过在所有线程完成后释放SyncObj来解决这个问题


由于我无法访问您的代码,我不能再多说了,但可能TMonitor使用的某个对象实例没有被释放。

我一直在研究如何实现
TMonitor
锁,最后我有了一个有趣的发现。为了戏剧化一点,我先告诉你锁是如何工作的

TObject
上调用任何
TMonitor
函数时,将创建
TMonitor
记录的新实例,并将该实例分配给对象本身内部的
MonitorFld
。此分配是以线程安全的方式进行的,使用
InterlocatedCompareExchangePointer
。由于此技巧,
TObject
仅包含一个指针大小的数据量,以支持
TMonitor
,因此它不包含完整的TMonitor结构。这是件好事

t监视器
结构包含许多记录。我们将从
FLockCount:Integer
字段开始。当第一个线程在任何对象上使用
TMonitor.Enter()
时,此组合锁计数器字段的值为零。再次使用
联锁比较交换
方法获取锁并启动计数器。调用线程不会锁定,也不会切换上下文,因为这都是在进程中完成的

当第二个线程尝试
TMonitor.Enter()
同一对象时,它的第一次锁定尝试将失败。当这种情况发生时,德尔福遵循两种策略:

  • 如果开发人员使用
    TMonitor.SetSpinCount()
    设置“旋转”次数,那么Delphi将执行一个繁忙的等待循环,旋转给定次数。这对于小型锁来说非常好,因为它允许在不进行上下文切换的情况下获取锁
  • 如果旋转计数过期(或者没有旋转计数,默认情况下旋转计数为零),
    TMonitor.Enter()
    将启动对
    TMonitor.GetEvent()返回的事件的等待。换句话说,它不会忙着等待,浪费CPU周期。记住
    TMonitor.GetEvent()
    ,因为这非常重要
假设我们有一个线程获取了锁,还有一个线程试图获取锁,但现在正在等待
TMonitor.GetEvent
返回的事件。当第一个线程调用
TMonitor.Exit()
时,它会注意到(通过
FLockCount
字段)至少还有一个线程阻塞。因此,它会立即为通常应该是先前分配的事件(调用
TMonitor.GetEvent()
)提供脉冲。但是,由于调用
TMonitor.Exit()
和调用
TMonitor.Enter()
的两个线程实际上可能同时调用
TMonitor.GetEvent()
,因此在
TMonitor.GetEvent()
中还有一些技巧可以确保只分配一个事件,与操作顺序无关

为了获得更多有趣的时刻,我们现在将深入研究
TMonitor.GetEvent()
的工作方式。这个东西存在于
系统
单元中(你知道,我们无法重新编译以使用它),但它通过
系统.MonitorSupport
指针将实际分配事件的任务委托给了另一个单元。指向类型为
TMonitorSupport
的记录,该记录声明了5个函数指针:

  • NewSyncObject
    -为同步目的分配新事件
  • FreeSyncObject
    -解除分配用于同步目的的事件
  • NewWaitObject
    -为等待操作分配新事件
  • FreeWaitObject
    -取消分配等待事件
  • 等待对象
    -井。。等待或发出信号
另外,
NewXYZ
函数返回的对象可以是任何对象,因为它们只用于调用
WaitXYZ
和相应的调用
FreeXyzObject
。在
SysUtils
中实现这些功能的方式旨在为这些锁提供最少的锁定和上下文切换;因为对象本身(由
NewSyncObject
NewWaitObject
返回)不是直接由
CreateEvent()
返回的事件,而是指向
SyncEventCacheArray
中记录的指针。更进一步,直到需要时才创建实际的Windows事件。因此,
SyncEventCacheArray
中的记录包含两条记录:

  • TSyncEventItem.Lock
    -这告诉Delphi锁现在是否用于任何事情,并且
  • TSyncEventItem.Event
    -如果需要等待,它保存将用于同步的实际事件
当应用程序终止时
  procedure CleanEventList(var EventCache: array of TSyncEventItem);
  var
    I: Integer;
  begin
    for I := Low(EventCache) to High(EventCache) do
    begin
      if InterlockedCompareExchange(EventCache[I].Lock, 1, 0) = 0 then
         DeleteSyncWaitObj(EventCache[I].Event);
      //repeat until InterlockedCompareExchange(EventCache[I].Lock, 1, 0) = 0;
      //DeleteSyncWaitObj(EventCache[I].Event);
    end;
  end;
{$IFNDEF VER230}
!!!!!!!!!!!!!!!!
You need to update this unit to fix the bug at line 19868
See http://stackoverflow.com/questions/14217735/application-hangs-in-sysutils-donemonitorsupport-on-exit
!!!!!!!!!!!!!!!!
{$ENDIF}