我有一些“FreeOnTerminate
”工作线程,它们在开始执行时将其句柄添加到TThreadList
上,在执行结束时将其从句柄中删除。他们还检查一个全局事件对象,该对象将通知他们取消工作。
以下是在主线程中运行的部分,该部分发出事件信号并等待可能的辅助线程结束。 WorkerHandleList
是全局ThreadList
。
...
procedure WaitForWorkers;
var
ThreadHandleList: TList;
begin
ThreadHandleList := TWorkerThread.WorkerHandleList.LockList;
TWorkerThread.WorkerHandleList.UnlockList;
WaitForMultipleObjects(ThreadHandleList.Count,
PWOHandleArray(ThreadHandleList.List), True, INFINITE);
end;
initialization
TWorkerThread.RecallAllWorkers := TEvent.Create;
TWorkerThread.WorkerHandleList := TThreadList.Create;
finalization
TWorkerThread.RecallAllWorkers.SetEvent;
WaitForWorkers;
TWorkerThread.RecallAllWorkers.Free;
TWorkerThread.WorkerHandleList.Free;
我认为这种设计的缺陷在于,我必须在等待线程的句柄之前就将列表解锁,因为这将导致死锁,因为线程本身将从同一列表中删除了它们的句柄。没有任何锁,上下文切换可能会导致线程释放自身,从而使
WaitForMultipleObjects
立即与WAIT_FAILED
返回。我也不能使用另一个锁,因为WaitForMultipleObjects
处于阻塞状态,并且我将无法从主线程释放该锁。我可以通过多种方式修改此设计,包括不使用
FreeOnTerminate
线程,这将保证有效的句柄,直到它们被明确释放为止。或仅从主线程修改线程句柄列表。也许其他...但是我想问的是,在不更改设计的情况下是否可以解决此类问题?例如,在将工作线程从列表中删除它们的句柄之前睡在工作线程代码中,或者调用
SwitchToThread
会导致所有非工作线程都运行吗?足够运行吗? 最佳答案
您对LockList()
的使用是错误的,并且很危险。调用UnlockList()
时,TList
不再 protected ,并且随着工作线程从列表中将其自身删除而被修改。这可能在您有机会调用WaitForMultipleObjects()
之前发生,或更糟糕的是在为其设置调用堆栈时。
您需要做的是锁定列表,将句柄复制到本地数组,解锁列表,然后在数组上等待。不要直接等待TList
本身。
procedure WaitForWorkers;
var
ThreadHandleList: TList;
ThreadHandleArr: array of THandle;
begin
ThreadHandleList := TWorkerThread.WorkerHandleList.LockList;
try
SetLength(ThreadHandleArr, ThreadHandleList.Count);
for I := 0 to ThreadHandleList.Count-1 do
ThreadHandleArr[i] := ThreadHandleList[i];
finally
TWorkerThread.WorkerHandleList.UnlockList;
end;
WaitForMultipleObjects(Length(ThreadHandleArr), PWOHandleArray(ThreadHandleArr), True, INFINITE);
end;
但是,即使有比赛条件。在实际输入
WaitForMultipleObjects()
之前,某些工作线程可能已经终止,因此破坏了它们的句柄。其余线程将在运行时破坏其句柄。无论哪种方式,它都会失败。在积极等待线程句柄的同时,您无法销毁它们。FreeOnTerminate=True
只能安全地用于您启动的线程,然后甚至忘记存在。当仍然出于任何原因仍需要访问来访问线程时,使用FreeOnTerminate=True
是非常危险的(特别是因为这一警告,当TThread.WaitFor()
时FreeOnTerminate=True
倾向于崩溃-线程句柄甚至TThread
对象本身在被破坏时都会被破坏仍在使用!)。您需要重新考虑您的等待策略。我可以想到一些替代方案:
WaitForMultipleObjects()
。仅定期定期重新锁定列表并检查列表是否为空是更安全,但效率较低:procedure WaitForWorkers;
var
ThreadHandleList: TList;
begin
repeat
ThreadHandleList := TWorkerThread.WorkerHandleList.LockList;
try
if ThreadHandleList.Count = 0 then Exit;
finally
TWorkerThread.WorkerHandleList.UnlockList;
end;
Sleep(500);
until False;
end;
WorkerHandleList
,并使用信号量或互锁计数器来跟踪已创建多少线程但尚未销毁它们。当信号量/计数器指示不存在更多线程时,退出等待。 WorkerHandleList
,但是等待手动重置事件,该事件在将第一个线程添加到列表时重置(在线程构造函数中执行此操作,而不是在Execute()
中),并在从中删除最后一个线程时发出信号列表(在线程析构函数中执行此操作,而不是在Execute()
或DoTerminate()
中执行此操作)。