我有一些" 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
会导致所有非工作线程都运行?够跑了吗?
答案 0 :(得分:10)
您对LockList()
的使用是错误的,也是危险的。一旦调用UnlockList()
,TList
就不再受到保护,并且会在工作线程从列表中删除时自行修改。这可能发生在您有机会呼叫WaitForMultipleObjects()
之前,或者更糟糕的是 WHILE 为其设置调用堆栈。
您需要做的是锁定列表,将句柄复制到本地阵列,解锁列表,然后等待阵列。不要直接等待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()
之前,某些工作线程可能已经终止,从而销毁了它们的句柄。剩下的线程将销毁它正在运行的 WHILE 句柄。无论哪种方式,都失败了。在您正在等待它们时,您不能销毁线程句柄。
FreeOnTerminate=True
只能安全地用于您启动的线程,然后忘记存在。当你仍然需要访问任何原因的线程时使用FreeOnTerminate=True
是非常危险的(特别是因为TThread.WaitFor()
FreeOnTerminate=True
在{{1}时容易崩溃的警告 - 线程句柄甚至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
,但等待第一个线程添加到列表时重置的手动重置事件(在线程构造函数中执行,而不是{{1} 1}})并在从列表中删除最后一个线程时发出信号(在线程析构函数中执行,而不是Execute()
或Execute()
)。
答案 1 :(得分:5)
假设一些事情(只有主线程会启动次要线程,其中),解决问题的最简单方法是这样的:
procedure WaitForWorkers;
var
ThreadHandleList: TList;
iItemCount : Integer;
begin
repeat
ThreadHandleList := TWorkerThread.WorkerHandleList.LockList;
try
iItemCount := ThreadHandleList.Count
finally
TWorkerThread.WorkerHandleList.UnlockList;
end;
if iItemCount = 0 then
BREAK;
sleep(Whatever_is_suitable);
until False;
end;
如果浪费任何cpu周期,或者等待时间超过必要时间,则可以创建一个事件并等待它,并让所有线程通过相同的函数从列表中删除它们。
procedure WaitForWorkers;
begin
Event.WaitFor(INFINITE);
end;
procedure RemoveHandleFromList(AHandle : THandle);
var
ThreadHandleList: TList;
idx : Integer;
begin
ThreadHandleList := TWorkerThread.WorkerHandleList.LockList;
try
idx := ThreadHandleList.IndexOf(Pointer(AHandle));
if idx >= 0 then
begin
ThreadHandleList.Delete(idx);
if ThreadHandleList.Count = 0 then
Event.SetEvent;
end;
finally
TWorkerThread.WorkerHandleList.UnlockList;
end;
end;
在这种情况下,您可能希望使用手动重置事件并在“AddHandleToList”过程中重置它。