2016-09-07 5 views
11

В следующей тестовой программе каждый тестовый поток добавляет свой дескриптор в глобальный TThreadList, когда он начинает выполнение, и удаляет свой дескриптор из того же списка, когда его выполнение вот-вот закончится.Почему WaitForMultipleObjects терпит неудачу при использовании нескольких ручек потоков?

В целях тестирования, кроме того, каждая нить обеспечивает добавление ее рукоятки до того, как основной поток заблокирует список (чтобы дублировать их ручки и начать ждать их завершения). В потоках также убедитесь, что они не удаляют свои ручки до того, как основной поток заблокирует список.

Тест-программа работает нормально около 50-60 потоков. После этого вызов WaitForMultipleObjects начинает сбой с WAIT_FAILED, GetLastError возвращает 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. 

ответ

21

В WaitForMultipleObjects()documentation состояния:

Максимальное количество объектов дескрипторов MAXIMUM_WAIT_OBJECTS.

Значение MAXIMUM_WAIT_OBJECTS 64 (определено в winnt.h), так что 100 ручки превышает предел. Тем не менее, эта документация также объясняет, как преодолеть этот предел:

ждать более чем MAXIMUM_WAIT_OBJECTS ручки, используйте один из следующих способов:

  • Создать нить ждать на MAXIMUM_WAIT_OBJECTS ручек, то ждите эту нить плюс другие ручки. Используйте этот метод, чтобы разбить дескрипторы на группы MAXIMUM_WAIT_OBJECTS.

  • Звоните в регистрWaitForSingleObject, чтобы ждать каждого дескриптора. Ожидающий поток из пула потоков ожидает MAXIMUM_WAIT_OBJECTS зарегистрированных объектов и назначает рабочий поток после того, как объект сигнализирован или истекает интервал ожидания.

Смотрите ответ на this question на примере первой методики.

+3

Существует также возможность использования вариации [этого подхода] (http://tondrej.blogspot.com/2016/03/the-strange-limitation-of-64-threads.html): interlocked-incremented/декрементированный счетчик и одно событие для ожидания. –

+0

Спасибо за редактирование. Я чувствую себя немного плохо, беря кредит за лучший ответ, чем мой :-) –

+1

Также обратите внимание, что ограничение на 64 объекта на самом деле является боковым пределом ядра. Переопределение MAXIMUM_WAIT_OBJECTS или даже переопределение пользовательской стороны WaitForMultipleObjects не будет делать многого. Вам просто нужно обойти этот конкретный предел. –

Смежные вопросы