Recent

Author Topic: Wait for multiple events  (Read 1330 times)

alpine

  • Hero Member
  • *****
  • Posts: 1379
Wait for multiple events
« on: September 01, 2024, 11:08:58 pm »
Hi,

Is there a portable way to wait for multiple TEventObject without using an OS-specific call such as e.g. WaitForMultipleObjects?

Thanks in advance!
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Wallaby

  • Full Member
  • ***
  • Posts: 104
Re: Wait for multiple events
« Reply #1 on: September 02, 2024, 01:02:52 pm »
Nope. And apparently there is no easy way to do it, I have checked in Delphi, waiting for multiple events is only available for Windows.


Joanna from IRC

  • Hero Member
  • *****
  • Posts: 1362
Re: Wait for multiple events
« Reply #2 on: September 02, 2024, 01:15:10 pm »
Hi,

Is there a portable way to wait for multiple TEventObject without using an OS-specific call such as e.g. WaitForMultipleObjects?

Thanks in advance!

I’m curious what controls you are working with ?
« Last Edit: September 02, 2024, 06:01:56 pm by Joanna »
✨ 🙋🏻‍♀️ More Pascal enthusiasts are needed on IRC .. https://libera.chat/guides/ IRC.LIBERA.CHAT  Ports [6667 plaintext ] or [6697 secure] channel #fpc  #pascal Please private Message me if you have any questions or need assistance. 💁🏻‍♀️

Thaddy

  • Hero Member
  • *****
  • Posts: 16823
  • Ceterum censeo Trump esse delendam
Re: Wait for multiple events
« Reply #3 on: September 02, 2024, 03:14:30 pm »
There are several ways to simulate it though, using pthread mutexes, but also through this library:
https://github.com/NeoSmart/PEvents
which looks like being able to be translated/flattened to FreePascal for Posix platforms.
Changing servers. thaddy.com may be temporary unreachable but restored when the domain name transfer is done.

flowCRANE

  • Hero Member
  • *****
  • Posts: 917
Re: Wait for multiple events
« Reply #4 on: February 20, 2025, 01:59:35 pm »
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:

Code: Pascal  [Select][+][-]
  1. uses
  2.   Windows;
  3.  
  4. const
  5.   THREAD_NUM = 4;
  6.  
  7. type
  8.   PParameter = ^TParameter;
  9.   TParameter = record
  10.     Index:      LONG;
  11.     Terminated: BOOL;
  12.   end;
  13.  
  14. var
  15.   Thread:      array [0 .. THREAD_NUM - 1] of HANDLE;
  16.   ThreadID:    array [0 .. THREAD_NUM - 1] of DWORD;
  17.   Parameter:   array [0 .. THREAD_NUM - 1] of TParameter;
  18.   EventStart:  array [0 .. THREAD_NUM - 1] of HANDLE;
  19.   EventFinish: array [0 .. THREAD_NUM - 1] of HANDLE;
  20.  
  21.   function ThreadFunc (AParameter: PParameter): DWORD; stdcall;
  22.   begin
  23.     repeat
  24.       WaitForSingleObject(EventStart[AParameter^.Index], INFINITE);
  25.  
  26.       if not AParameter^.Terminated then
  27.       begin
  28.         WriteLn('Thread ', AParameter^.Index, ' did its job.');
  29.         SetEvent(EventFinish[AParameter^.Index]);
  30.       end;
  31.     until AParameter^.Terminated;
  32.  
  33.     Result := 0;
  34.   end;
  35.  
  36. var
  37.   Index:      LONG;
  38.   IndexEvent: LONG;
  39. begin
  40.   for Index := 0 to THREAD_NUM - 1 do
  41.   begin
  42.     Parameter[Index].Index      := Index;
  43.     Parameter[Index].Terminated := False;
  44.  
  45.     EventStart[Index]  := CreateEvent(nil, False, False, '');
  46.     EventFinish[Index] := CreateEvent(nil, False, False, '');
  47.  
  48.     Thread[Index] := CreateThread(nil, 0, @ThreadFunc, @Parameter[Index], 0, ThreadID[Index]);
  49.   end;
  50.  
  51.   for Index := 1 to 5 do
  52.   begin
  53.     for IndexEvent := 0 to THREAD_NUM - 1 do
  54.       SetEvent(EventStart[IndexEvent]);
  55.  
  56.     WaitForMultipleObjects(THREAD_NUM, @EventFinish, True, INFINITE);
  57.     WriteLn();
  58.   end;
  59.  
  60.   for Index := 0 to THREAD_NUM - 1 do
  61.   begin
  62.     Parameter[Index].Terminated := True;
  63.     SetEvent(EventStart[Index]);
  64.   end;
  65.  
  66.   WaitForMultipleObjects(THREAD_NUM, @Thread, True, INFINITE);
  67.  
  68.   for Index := 0 to THREAD_NUM - 1 do
  69.   begin
  70.     CloseHandle(Thread[Index]);
  71.     CloseHandle(EventStart[Index]);
  72.     CloseHandle(EventFinish[Index]);
  73.   end;
  74.  
  75.   ReadLn();
  76. end.

And here is the same but using Free Pascal classes:

Code: Pascal  [Select][+][-]
  1. uses
  2.   SysUtils,
  3.   Classes,
  4.   SyncObjs;
  5.  
  6. type
  7.   TWorkerThread = class(TThread)
  8.   private
  9.     FIndex: Integer;
  10.   protected
  11.     procedure Execute(); override;
  12.   public
  13.     constructor Create(AIndex: Integer);
  14.   end;
  15.  
  16. const
  17.   THREAD_NUM = 4;
  18.  
  19. var
  20.   Threads:     array [0 .. THREAD_NUM - 1] of TWorkerThread;
  21.   EventStart:  array [0 .. THREAD_NUM - 1] of TEvent;
  22.   EventFinish: array [0 .. THREAD_NUM - 1] of TEvent;
  23.  
  24.   constructor TWorkerThread.Create(AIndex: Integer);
  25.   begin
  26.     FIndex := AIndex;
  27.     inherited Create(False);
  28.   end;
  29.  
  30.   procedure TWorkerThread.Execute();
  31.   begin
  32.     repeat
  33.       EventStart[FIndex].WaitFor(INFINITE);
  34.  
  35.       if not Terminated then
  36.       begin
  37.         WriteLn('Thread ', FIndex, ' did some job.');
  38.         EventFinish[FIndex].SetEvent();
  39.       end;
  40.     until Terminated;
  41.   end;
  42.  
  43. var
  44.   Index:      Integer;
  45.   IndexEvent: Integer;
  46. begin
  47.   for Index := 0 to THREAD_NUM - 1 do
  48.   begin
  49.     EventStart[Index]  := TEvent.Create(nil, False, False, '');
  50.     EventFinish[Index] := TEvent.Create(nil, False, False, '');
  51.  
  52.     Threads[Index] := TWorkerThread.Create(Index);
  53.   end;
  54.  
  55.   for Index := 1 to 5 do
  56.   begin
  57.     for IndexEvent := 0 to THREAD_NUM - 1 do
  58.       EventStart[IndexEvent].SetEvent();
  59.  
  60.     for IndexEvent := 0 to THREAD_NUM - 1 do
  61.       EventFinish[IndexEvent].WaitFor(INFINITE);
  62.  
  63.     WriteLn();
  64.   end;
  65.  
  66.   for Index := 0 to THREAD_NUM - 1 do
  67.   begin
  68.     Threads[Index].Terminate();
  69.     EventStart[Index].SetEvent();
  70.  
  71.     Threads[Index].WaitFor();
  72.     Threads[Index].Free();
  73.  
  74.     EventStart[Index].Free();
  75.     EventFinish[Index].Free();
  76.   end;
  77.  
  78.   ReadLn();
  79. end.

Example output:

Code: Pascal  [Select][+][-]
  1. Thread 0 did its job.
  2. Thread 2 did its job.
  3. Thread 1 did its job.
  4. Thread 3 did its job.
  5.  
  6. Thread 0 did its job.
  7. Thread 1 did its job.
  8. Thread 2 did its job.
  9. Thread 3 did its job.
  10.  
  11. Thread 0 did its job.
  12. Thread 3 did its job.
  13. Thread 2 did its job.
  14. Thread 1 did its job.
  15.  
  16. Thread 0 did its job.
  17. Thread 2 did its job.
  18. Thread 3 did its job.
  19. Thread 1 did its job.
  20.  
  21. Thread 1 did its job.
  22. Thread 0 did its job.
  23. Thread 2 did its job.
  24. 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.
« Last Edit: February 20, 2025, 02:10:00 pm by flowCRANE »
Lazarus 3.6 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on a retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

marcov

  • Administrator
  • Hero Member
  • *
  • Posts: 12161
  • FPC developer.
Re: Wait for multiple events
« Reply #5 on: February 20, 2025, 02:12:16 pm »
Flowcrane: such workarounds are not for all scenarios.

waitformultiple can also exit if one is signaled, and then you can process that one, and block on the remaining ones, enabling more parallelism.

Maybe the ntsync kernel module or futex_waitv will finally enable something comparable for Linux. However till now those seem mostly meant for Windows emulation (read "wine"), not general purpose app use.

flowCRANE

  • Hero Member
  • *****
  • Posts: 917
Re: Wait for multiple events
« Reply #6 on: February 20, 2025, 06:57:00 pm »
Flowcrane: such workarounds are not for all scenarios.

Of course but, there is no one universal answer to this question. You can wait for multiple signals without using anything fancy, like in the scenario my test program implements. In other scenarios, this won't be an option. To be able to provide specific advice, one would need to know the exact context, and the OP did not describe this.

Who knows what he need to implement. Maybe my test program will be useful to him, or maybe to someone else. In any case, in order to give any sensible advice, the OP needs to provide more information about what exactly he wants to achieve.
Lazarus 3.6 with FPC 3.2.2, Windows 10 — all 64-bit

Working solo on a retro-style action/adventure game (pixel art), programming the engine from scratch, using Free Pascal and SDL3.

alpine

  • Hero Member
  • *****
  • Posts: 1379
Re: Wait for multiple events
« Reply #7 on: February 20, 2025, 08:11:35 pm »
Flowcrane: such workarounds are not for all scenarios.

Of course but, there is no one universal answer to this question. You can wait for multiple signals without using anything fancy, like in the scenario my test program implements. In other scenarios, this won't be an option. To be able to provide specific advice, one would need to know the exact context, and the OP did not describe this.

Who knows what he need to implement. Maybe my test program will be useful to him, or maybe to someone else. In any case, in order to give any sensible advice, the OP needs to provide more information about what exactly he wants to achieve.
I apologise for not being so specific, the use case was the one that resembles the select semantics, as marcov suggests. Not so trivial as the one for waiting all events set. The question was for a portable way to do this.

Anyway, I appreciate the help.
 
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

 

TinyPortal © 2005-2018