2016-09-07 24 views
11

W poniższym programie testowym każdy wątek testowy dodaje swój uchwyt do globalnego TThreadList, gdy rozpoczyna wykonywanie, i usuwa jego uchwyt z tej samej listy, gdy jego zakończenie ma się wkrótce zakończyć.Dlaczego funkcja WaitForMultipleObjects kończy się niepowodzeniem z wieloma uchwytami wątków?

Dla celów testowania dodatkowo każdy wątek upewnia się, że dodaje swój uchwyt, zanim główny wątek zablokuje listę (aby powielić swoje uchwyty i zacząć na nich czekać, aby zakończyć). Gwinty upewniają się również, że nie usuwają uchwytów, zanim główny wątek nie zablokuje listy.

Program testowy działa poprawnie dla około 50-60 wątków. Po tym, połączenie WaitForMultipleObjects zaczyna się niepowodzeniem z WAIT_FAILED, GetLastError zwraca 87 (ERROR_INVALID_PARAMETER). Obecnie rozpoczyna 100 wątków. Moje pytanie brzmi: co robię źle?

Program jest kompilowany za pomocą XE2 - aktualizacja 4, 32-bitowa platforma docelowa. Pole testowe to 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. 

Odpowiedz

21

W WaitForMultipleObjects()documentation stany:

Maksymalna ilość uchwytów przedmiotu jest MAXIMUM_WAIT_OBJECTS.

Wartość MAXIMUM_WAIT_OBJECTS wynosi 64 (zdefiniowane w winnt.h), więc 100 uchwytów przekracza limit. Jednak dokumentacja wyjaśnia również, jak przezwyciężyć tę granicę:

czekać na więcej niż MAXIMUM_WAIT_OBJECTS uchwytami, użyj jednej z następujących metod:

  • utworzyć wątku czekać na MAXIMUM_WAIT_OBJECTS uchwytów, a następnie czekaj na ten wątek plus inne uchwyty. Użyj tej techniki, aby podzielić uchwyty na grupy o wartości MAXIMUM_WAIT_OBJECTS.

  • Zadzwoń RegisterWaitForSingleObject, aby czekać na każdym uchwycie. Wątek oczekiwania z puli wątków czeka na zarejestrowanych obiektach MAXIMUM_WAIT_OBJECTS i przypisuje wątek roboczy po sygnalizacji obiektu lub upłynięciu limitu czasu.

Patrz odpowiedź na this question dla przykładu pierwszego techniką.

+3

Istnieje również możliwość użycia wariantu [tego podejścia] (http://tondrej.blogspot.com/2016/03/the-strange-limitation-of-64-threads.html): interlocked-incremented/dekrementowany licznik i pojedyncze wydarzenie, na które trzeba czekać. –

+0

Dzięki za edycję. Czuję się trochę źle biorąc kredyt za lepszą odpowiedź niż moja :-) –

+1

Należy również zauważyć, że limit 64 obiektów jest w rzeczywistości ograniczeniem po stronie jądra. Przedefiniowanie MAXIMUM_WAIT_OBJECTS lub nawet ponowne wprowadzenie strony użytkownika WaitForMultipleObjects nie przyniesie wiele. Trzeba po prostu obejść ten konkretny limit. –

Powiązane problemy