Tim*_*Tim 10 delphi access-violation threadpool omnithreadlibrary
在我们的Delphi XE4应用程序中,我们使用具有MaxExecuting = 4的OmniThreadPool来提高某个计算的效率.不幸的是,我们遇到了间歇性访问违规问题(例如,参见下面的MadExcept错误报告http://ec2-72-44-42-247.compute-1.amazonaws.com/BugReport.txt).我能够构建以下示例来演示该问题.运行以下控制台应用程序后,System.SyncObjs.TCriticalSection.Acquire中的访问冲突通常会在一分钟左右内发生.任何人都可以告诉我在下面的代码中我做错了什么,或者告诉我另一种方法来实现预期的结果?
program OmniPoolCrashTest;
{$APPTYPE CONSOLE}
uses
Winapi.Windows, System.SysUtils,
DSiWin32, GpLists,
OtlSync, OtlThreadPool, OtlTaskControl, OtlComm, OtlTask;
const
cTimeToWaitForException = 10 * 60 * 1000; // program exits if no exception after 10 minutes
MSG_CALLEE_FINISHED = 113; // our custom Omni message ID
cMaxAllowedParallelCallees = 4; // enforced via thread pool
cCalleeDuration = 10; // 10 miliseconds
cCallerRepetitionInterval = 200; // 200 milliseconds
cDefaultNumberOfCallers = 10; // 10 callers each issuing 1 call every 200 milliseconds
var
gv_OmniThreadPool : IOmniThreadPool;
procedure OmniTaskProcedure_Callee(const task: IOmniTask);
begin
Sleep(cCalleeDuration);
task.Comm.Send(MSG_CALLEE_FINISHED);
end;
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).Schedule(gv_OmniThreadPool);
WaitForSingleObject(OmniTaskControl.Comm.NewMessageEvent, INFINITE);
end;
procedure OmniTaskProcedure_Caller(const task: IOmniTask);
begin
while not task.Terminated do begin
PerformThreadPoolTest();
Sleep(cCallerRepetitionInterval);
end;
end;
var
CallerTasks : TGpInterfaceList<IOmniTaskControl>;
i : integer;
begin
gv_OmniThreadPool := CreateThreadPool('CalleeThreadPool');
gv_OmniThreadPool.MaxExecuting := cMaxAllowedParallelCallees;
CallerTasks := TGpInterfaceList<IOmniTaskControl>.Create();
for i := 1 to StrToIntDef(ParamStr(1), cDefaultNumberOfCallers) do begin
CallerTasks.Add( CreateTask(OmniTaskProcedure_Caller).Run() );
end;
Sleep(cTimeToWaitForException);
for i := 0 to CallerTasks.Count-1 do begin
CallerTasks[i].Terminate();
end;
CallerTasks.Free();
end.
Run Code Online (Sandbox Code Playgroud)
您在这里有一个难以找到的任务控制器需要所有者问题的示例.发生的情况是,任务控制器有时会在任务本身之前被销毁,并导致任务访问包含随机数据的内存.
有问题的场景是这样的([T]标记任务,[C]标记任务控制器):
在Graymatter的解决方法中,为OmniThreadLibrary中的OnTerminated任务创建一个隐式所有者,该任务"解决"了问题.
等待任务完成的正确方法是调用taskControler.WaitFor.
procedure OmniTaskProcedure_Callee(const task: IOmniTask);
begin
Sleep(cCalleeDuration);
end;
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).Schedule(gv_OmniThreadPool);
OmniTaskControl.WaitFor(INFINITE);
end;
Run Code Online (Sandbox Code Playgroud)
我将考虑用引用计数解决方案替换共享内存记录,这将防止此类问题(或至少使它们更容易找到).
看起来您的终止消息导致了问题.删除消息和WaitForSingleObject停止了AV.在我的测试中,只需在.Schedule之前添加.OnTerminated(过程开始结束)也足以改变流程并停止错误.所以这种情况下的代码看起来像这样:
procedure PerformThreadPoolTest();
var
OmniTaskControl : IOmniTaskControl;
begin
OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).OnTerminated(procedure begin end).Schedule(gv_OmniThreadPool);
WaitForSingleObject(OmniTaskControl.Comm.NewMessageEvent, INFINITE);
end;
Run Code Online (Sandbox Code Playgroud)
在我看来,这可能是问题所在.otSharedInfo_ref有一个名为MonitorLock的属性.这用于阻止对otSharedInfo_ref的更改.如果由于某种原因otSharedInfo_ref在获取等待时被释放,那么你可能会得到一些非常奇怪的行为
它的代码如下所示:
procedure TOmniTask.InternalExecute(calledFromTerminate: boolean);
begin
...
// with internal monitoring this will not be processed if the task controller owner is also shutting down
sync := nil; // to remove the warning in the 'finally' clause below
otSharedInfo_ref.MonitorLock.Acquire;
try
sync := otSharedInfo_ref.MonitorLock.SyncObj;
if assigned(otSharedInfo_ref.Monitor) then
otSharedInfo_ref.Monitor.Send(COmniTaskMsg_Terminated,
integer(Int64Rec(UniqueID).Lo), integer(Int64Rec(UniqueID).Hi));
otSharedInfo_ref := nil;
finally sync.Release; end;
...
end; { TOmniTask.InternalExecute }
Run Code Online (Sandbox Code Playgroud)
如果otSharedInfo_ref.MonitorLock.Acquire正忙着等待并且otSharedInfo_ref后面的对象被释放,那么我们最终会处于一个非常讨厌的地方.将代码更改为此会停止在InternalExecute中发生的AV:
procedure TOmniTask.InternalExecute(calledFromTerminate: boolean);
var
...
monitorLock: TOmniCS;
...
begin
...
// with internal monitoring this will not be processed if the task controller owner is also shutting down
sync := nil; // to remove the warning in the 'finally' clause below
monitorLock := otSharedInfo_ref.MonitorLock;
monitorLock.Acquire;
try
sync := monitorLock.SyncObj;
if assigned(otSharedInfo_ref) and assigned(otSharedInfo_ref.Monitor) then
otSharedInfo_ref.Monitor.Send(COmniTaskMsg_Terminated,
integer(Int64Rec(UniqueID).Lo), integer(Int64Rec(UniqueID).Hi));
otSharedInfo_ref := nil;
finally sync.Release; end;
...
end; { TOmniTask.InternalExecute }
Run Code Online (Sandbox Code Playgroud)
我确实开始在OmniTaskProcedure_Callee方法中获取AV,然后在"task.Comm.Send(MSG_CALLEE_FINISHED)"行中,所以它仍然没有修复,但这应该有助于其他人/ Primoz进一步确定正在发生的事情.在新错误中,task.Comm通常是未分配的.
| 归档时间: |
|
| 查看次数: |
576 次 |
| 最近记录: |