【问题标题】:Why does WaitForMultipleObjects fail with multiple thread handles?为什么 WaitForMultipleObjects 会因多个线程句柄而失败?
【发布时间】:2016-09-07 22:49:51
【问题描述】:

在以下测试程序中,每个测试线程在开始执行时将其句柄添加到全局TThreadList,并在其执行即将结束时从同一列表中删除其句柄。

另外,出于测试目的,每个线程确保在主线程锁定列表之前添加其句柄(以复制它们的句柄并开始等待它们完成)。线程还确保在主线程锁定列表之前它们不会删除它们的句柄。

测试程序可以正常运行大约 50-60 个线程。之后,WaitForMultipleObjects 调用开始失败,WAIT_FAILEDGetLastError 返回 87 (ERROR_INVALID_PARAMETER)。目前它启动了 100 个线程。我的问题是,我做错了什么?

程序是用 XE2 编译的 - 更新 4,32 位目标平台。测试框为 W7x64。

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  windows,
  sysutils,
  classes,
  syncobjs;

type
  TTestThread = class(TThread)
  private
    FAckStarted: TEvent;
    function GetAckHandle: THandle;
    class var
      ThreadList: TThreadList;
      WaitEnd: THandle;
  protected
    procedure Execute; override;
  public
    constructor Create;
    destructor Destroy; override;
    property AckHandle: THandle read GetAckHandle;
  end;

{.$DEFINE FREEONTERMINATE}

constructor TTestThread.Create;
begin
  inherited Create(True);
  FAckStarted := TEvent.Create;
{$IFDEF FREEONTERMINATE}
  FreeOnTerminate := True;
{$ENDIF}
end;

destructor TTestThread.Destroy;
begin
  FAckStarted.Free;
  inherited;
end;

procedure TTestThread.Execute;
begin
//  OutputDebugString(PChar(Format('%d starting -------------', [Handle])));
  ThreadList.Add(Pointer(Handle));
  FAckStarted.SetEvent;

  NameThreadForDebugging(AnsiString(IntToStr(Handle)));

  WaitForSingleObject(WaitEnd, INFINITE);
  ThreadList.Remove(Pointer(Handle));
//  OutputDebugString(PChar(Format('%d leaving -------------', [Handle])));
end;

function TTestThread.GetAckHandle: THandle;
begin
  Result := FAckStarted.Handle;
end;

const
  NumThreads = 100;

var
  DeferThreadEnd: TEvent;
  ThreadList: array of TThread;
  i: Integer;
  Thread: TTestThread;
  WaitThreadStart: THandle;
  LockList: TList;
  LockListCount: Integer;
  ThreadHandleArr: array of THandle;
  WaitRet: DWORD;
begin
  IsMultiThread := True;
  ReportMemoryLeaksOnShutdown := True;

  TTestThread.ThreadList := TThreadList.Create;
  DeferThreadEnd := TEvent.Create;
  TTestThread.WaitEnd := DeferThreadEnd.Handle;

  SetLength(ThreadList, NumThreads);
  for i := 0 to NumThreads - 1 do begin
    Thread := TTestThread.Create;
    ThreadList[i] := Thread;
    WaitThreadStart := Thread.GetAckHandle;
    Thread.Start;
    WaitForSingleObject(WaitThreadStart, INFINITE);
  end;

  LockList := TTestThread.ThreadList.LockList;
  LockListCount := LockList.Count;
  SetLength(ThreadHandleArr, LockListCount);
  for i := 0 to LockListCount - 1 do
{$IFDEF FREEONTERMINATE}
    Win32Check(DuplicateHandle(GetCurrentProcess, THandle(LockList[i]),
            GetCurrentProcess, @ThreadHandleArr[i], SYNCHRONIZE, True, 0));
{$ELSE}
    ThreadHandleArr[i] := THandle(LockList[i]);
{$ENDIF}
  TTestThread.ThreadList.UnlockList;

  DeferThreadEnd.SetEvent;
  if LockListCount > 0 then begin
    Writeln('waiting for ', LockListCount, ' threads');
    WaitRet := WaitForMultipleObjects(LockListCount,
                              PWOHandleArray(ThreadHandleArr), True, INFINITE);
    case WaitRet of
      WAIT_OBJECT_0: Writeln('wait success');
      WAIT_FAILED: Writeln('wait fail:', SysErrorMessage(GetLastError));
    end;
  end;

  for i := 0 to Length(ThreadList) - 1 do begin
{$IFDEF FREEONTERMINATE}
    Win32Check(CloseHandle(ThreadHandleArr[i]));
{$ELSE}
    ThreadList[i].Free;
{$ENDIF}
  end;
  DeferThreadEnd.Free;
  TTestThread.ThreadList.Free;
  Writeln('program end');
  Readln;
end.

【问题讨论】:

    标签: multithreading delphi delphi-xe2


    【解决方案1】:

    WaitForMultipleObjects()documentation 声明:

    对象句柄的最大数量为 MAXIMUM_WAIT_OBJECTS。

    MAXIMUM_WAIT_OBJECTS 的值是 64(在winnt.h 中定义),因此 100 个句柄超出了限制。但是,该文档还解释了如何克服该限制:

    要等待超过 MAXIMUM_WAIT_OBJECTS 个句柄,请使用以下方法之一:

    • 创建一个线程以等待 MAXIMUM_WAIT_OBJECTS 个句柄,然后在该线程和其他句柄上等待。使用此技术将句柄分成 MAXIMUM_WAIT_OBJECTS 组。

    • 调用 RegisterWaitForSingleObject 以等待每个句柄。线程池中的等待线程等待 MAXIMUM_WAIT_OBJECTS 个注册对象,并在对象发出信号或超时间隔到期后分配一个工作线程。

    有关第一种技术的示例,请参阅this question 的答案。

    【讨论】:

    • 还可以使用this approach 的变体:联锁递增/递减计数器和等待单个事件。
    • 感谢编辑。得到比我更好的答案,我觉得有点不好:-)
    • 还要注意,64 个对象的限制实际上是内核端的限制。重新定义 MAXIMUM_WAIT_OBJECTS 甚至重新实现 WaitForMultipleObjects 的用户空间端都不会做太多事情。你只需要解决这个特定的限制。
    猜你喜欢
    • 2019-08-23
    • 2013-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-30
    • 2020-10-14
    相关资源
    最近更新 更多