Multithreading 应用程序挂起在SysUtils中->;DoneMonitor出口时的支持
我正在编写一个线程非常密集的应用程序,它在退出时挂起 我追踪了系统的各个单元,找到了程序进入无限循环的地方。它位于SysUtils行19868->DoneMonitorSupport->CleanEventList: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);
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报告:
以前有没有人遇到过类似的行为?你知道发现根本原因的任何策略吗?我可以用Cosmin提供的例子重现你的问题。我也可以通过在所有线程完成后释放SyncObj来解决这个问题
由于我无法访问您的代码,我不能再多说了,但可能TMonitor使用的某个对象实例没有被释放。我一直在研究如何实现
TMonitor
锁,最后我有了一个有趣的发现。为了戏剧化一点,我先告诉你锁是如何工作的
在TObject
上调用任何TMonitor
函数时,将创建TMonitor
记录的新实例,并将该实例分配给对象本身内部的MonitorFld
。此分配是以线程安全的方式进行的,使用InterlocatedCompareExchangePointer
。由于此技巧,TObject
仅包含一个指针大小的数据量,以支持TMonitor
,因此它不包含完整的TMonitor结构。这是件好事
此t监视器
结构包含许多记录。我们将从FLockCount:Integer
字段开始。当第一个线程在任何对象上使用TMonitor.Enter()
时,此组合锁计数器字段的值为零。再次使用联锁比较交换
方法获取锁并启动计数器。调用线程不会锁定,也不会切换上下文,因为这都是在进程中完成的
当第二个线程尝试TMonitor.Enter()
同一对象时,它的第一次锁定尝试将失败。当这种情况发生时,德尔福遵循两种策略:
- 如果开发人员使用
设置“旋转”次数,那么Delphi将执行一个繁忙的等待循环,旋转给定次数。这对于小型锁来说非常好,因为它允许在不进行上下文切换的情况下获取锁TMonitor.SetSpinCount()
- 如果旋转计数过期(或者没有旋转计数,默认情况下旋转计数为零),
将启动对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
中的记录包含两条记录:
-这告诉Delphi锁现在是否用于任何事情,并且TSyncEventItem.Lock
-如果需要等待,它保存将用于同步的实际事件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}