10

Delphi XE4 アプリケーションでは、特定の計算の効率を向上させるために、MaxExecuting=4 で OmniThreadPool を使用しています。残念ながら、断続的なアクセス違反で問題が発生しています (たとえば、次の MadExcept バグ レポートhttp://ec2-72-44-42-247.compute-1.amazonaws.com/BugReport.txtを参照してください)。問題を示す次の例を作成できました。次のコンソール アプリケーションを実行した後、System.SyncObjs.TCriticalSection.Acquire で通常 1 分ほどでアクセス違反が発生します。次のコードで私が間違っていることを誰かに教えてもらえますか、または望ましい結果を達成する別の方法を教えてもらえますか?

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.
4

2 に答える 2

5

終了メッセージが問題を引き起こしているようです。メッセージと WaitForSingleObject を削除すると、AV が停止しました。私のテストでは、 .Schedule の前に .OnTerminated(procedure begin end) を追加するだけで、フローを変更してエラーを停止するのに十分でした。したがって、その場合のコードは次のようになります。

procedure PerformThreadPoolTest();
var
  OmniTaskControl : IOmniTaskControl;
begin
  OmniTaskControl := CreateTask(OmniTaskProcedure_Callee).OnTerminated(procedure begin end).Schedule(gv_OmniThreadPool);
  WaitForSingleObject(OmniTaskControl.Comm.NewMessageEvent, INFINITE);
end;

これが問題かもしれないように私には見えます。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 }

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 }

私は OmniTaskProcedure_Callee メソッドで AV を取得し始め、次に "task.Comm.Send(MSG_CALLEE_FINISHED)" 行で AV を取得し始めたので、まだ修正されていませんが、これは他のユーザーや Primoz が何が起こっているのかをさらに特定するのに役立つはずです。新しいエラーでは、task.Comm が割り当てられていないことがよくあります。

于 2014-05-28T22:34:48.037 に答える