Is there a portable way to wait for multiple TEventObject without using an OS-specific call such as e.g. WaitForMultipleObjects?
You can wait for all of them in a normal loop, using the
WaitFor method. Regardless of the order in which the events are signaled, such a loop will end only if all events have been signaled.
I have a task like this, where the main thread signals the worker threads to perform distributed calculations and waits until they all finish — then the main thread is resumed and in the next frame this scenario repeats itself (this is for the game engine). The worker threads are created once and when they have nothing to do, they are to be frozen so that they do not waste CPU time.
Here is the test program that simulates such scenario — with no error checking, to simplify the tester. The main loop performs 5 times, uses 4 worker threads and self-resetting event objects. Worker threads pretend to do something and only display a message that they have completed the job. First in pure Win32 API:
uses
Windows;
const
THREAD_NUM = 4;
type
PParameter = ^TParameter;
TParameter = record
Index: LONG;
Terminated: BOOL;
end;
var
Thread: array [0 .. THREAD_NUM - 1] of HANDLE;
ThreadID: array [0 .. THREAD_NUM - 1] of DWORD;
Parameter: array [0 .. THREAD_NUM - 1] of TParameter;
EventStart: array [0 .. THREAD_NUM - 1] of HANDLE;
EventFinish: array [0 .. THREAD_NUM - 1] of HANDLE;
function ThreadFunc (AParameter: PParameter): DWORD; stdcall;
begin
repeat
WaitForSingleObject(EventStart[AParameter^.Index], INFINITE);
if not AParameter^.Terminated then
begin
WriteLn('Thread ', AParameter^.Index, ' did its job.');
SetEvent(EventFinish[AParameter^.Index]);
end;
until AParameter^.Terminated;
Result := 0;
end;
var
Index: LONG;
IndexEvent: LONG;
begin
for Index := 0 to THREAD_NUM - 1 do
begin
Parameter[Index].Index := Index;
Parameter[Index].Terminated := False;
EventStart[Index] := CreateEvent(nil, False, False, '');
EventFinish[Index] := CreateEvent(nil, False, False, '');
Thread[Index] := CreateThread(nil, 0, @ThreadFunc, @Parameter[Index], 0, ThreadID[Index]);
end;
for Index := 1 to 5 do
begin
for IndexEvent := 0 to THREAD_NUM - 1 do
SetEvent(EventStart[IndexEvent]);
WaitForMultipleObjects(THREAD_NUM, @EventFinish, True, INFINITE);
WriteLn();
end;
for Index := 0 to THREAD_NUM - 1 do
begin
Parameter[Index].Terminated := True;
SetEvent(EventStart[Index]);
end;
WaitForMultipleObjects(THREAD_NUM, @Thread, True, INFINITE);
for Index := 0 to THREAD_NUM - 1 do
begin
CloseHandle(Thread[Index]);
CloseHandle(EventStart[Index]);
CloseHandle(EventFinish[Index]);
end;
ReadLn();
end.
And here is the same but using Free Pascal classes:
uses
SysUtils,
Classes,
SyncObjs;
type
TWorkerThread = class(TThread)
private
FIndex: Integer;
protected
procedure Execute(); override;
public
constructor Create(AIndex: Integer);
end;
const
THREAD_NUM = 4;
var
Threads: array [0 .. THREAD_NUM - 1] of TWorkerThread;
EventStart: array [0 .. THREAD_NUM - 1] of TEvent;
EventFinish: array [0 .. THREAD_NUM - 1] of TEvent;
constructor TWorkerThread.Create(AIndex: Integer);
begin
FIndex := AIndex;
inherited Create(False);
end;
procedure TWorkerThread.Execute();
begin
repeat
EventStart[FIndex].WaitFor(INFINITE);
if not Terminated then
begin
WriteLn('Thread ', FIndex, ' did some job.');
EventFinish[FIndex].SetEvent();
end;
until Terminated;
end;
var
Index: Integer;
IndexEvent: Integer;
begin
for Index := 0 to THREAD_NUM - 1 do
begin
EventStart[Index] := TEvent.Create(nil, False, False, '');
EventFinish[Index] := TEvent.Create(nil, False, False, '');
Threads[Index] := TWorkerThread.Create(Index);
end;
for Index := 1 to 5 do
begin
for IndexEvent := 0 to THREAD_NUM - 1 do
EventStart[IndexEvent].SetEvent();
for IndexEvent := 0 to THREAD_NUM - 1 do
EventFinish[IndexEvent].WaitFor(INFINITE);
WriteLn();
end;
for Index := 0 to THREAD_NUM - 1 do
begin
Threads[Index].Terminate();
EventStart[Index].SetEvent();
Threads[Index].WaitFor();
Threads[Index].Free();
EventStart[Index].Free();
EventFinish[Index].Free();
end;
ReadLn();
end.
Example output:
Thread 0 did its job.
Thread 2 did its job.
Thread 1 did its job.
Thread 3 did its job.
Thread 0 did its job.
Thread 1 did its job.
Thread 2 did its job.
Thread 3 did its job.
Thread 0 did its job.
Thread 3 did its job.
Thread 2 did its job.
Thread 1 did its job.
Thread 0 did its job.
Thread 2 did its job.
Thread 3 did its job.
Thread 1 did its job.
Thread 1 did its job.
Thread 0 did its job.
Thread 2 did its job.
Thread 3 did its job.
You can play around and change the number of threads to higher. I haven't tested it on platforms other than Windows, but the effect should be exactly the same. I hope this helps you.