is invalid — it will end only if a condition variable is signaled; or the condition flag is set to true, or the SDL_WaitConditionTimeout will return true. So this loop will behave the same as the SDL_WaitCondition, just will wait forever.
I did some test (in Free Pascal) and to fix the problem and actually take the timeout into account, it must look like this:
while (!done) {
SDL_LockMutex(lock);
while (!condition) {
if (SDL_WaitConditionTimeout(cond, lock, timeout) == 0)
break;
}
SDL_UnlockMutex(lock);
if (condition) {
/* ... */
}
/* ... do some periodic work */
}
However, I have a problem understanding why a logical flag (the condition variable) is used at all to wait for a condition variable (type SDL_Condition) to be signaled, and why the SDL_WaitCondition and SDL_WaitConditionTimeout functions are called in a loop.
Can somebody explain me all of this mysteries and how to properly write a loop that waits for the signal and takes the timeout into account?
uses
SDL3;
var
Condition: PSDL_Condition = nil;
Mutex: PSDL_Mutex = nil;
Thread: PSDL_Thread = nil;
Flag: Boolean = False;
Terminated: Boolean = False;
function ThreadWorker (AData: Pointer): Integer; cdecl;
var
Signaled: Boolean;
begin
repeat
SDL_LockMutex(Mutex);
// This loop seems to do waiting in the right way.
while not Flag do
if not SDL_WaitConditionTimeout(Condition, Mutex, 500) then
break;
Signaled := Flag;
Flag := False;
SDL_UnlockMutex(Mutex);
if not Terminated then
begin
if Signaled then
WriteLn(' ', SDL_GetTicks():5, ' Extra work.');
WriteLn(SDL_GetTicks():5, ' Periodic work.');
end;
until Terminated;
Result := 0;
end;
label
CleanUp;
var
Window: PSDL_Window;
Event: TSDL_Event;
begin
SDL_Init(SDL_INIT_EVENTS);
Window := SDL_CreateWindow('', 320, 240, 0);
Thread := SDL_CreateThreadRuntime(@ThreadWorker, nil, nil, nil, nil);
Condition := SDL_CreateCondition();
Mutex := SDL_CreateMutex();
Flag := False;
while True do
begin
while SDL_PollEvent(@Event) do
case Event._Type of
SDL_EVENT_KEY_DOWN:
case Event.Key.Scancode of
SDL_SCANCODE_SPACE:
begin
SDL_LockMutex(Mutex);
Flag := True;
SDL_SignalCondition(Condition);
SDL_UnlockMutex(Mutex);
end;
SDL_SCANCODE_ESCAPE: goto CleanUp;
end;
SDL_EVENT_QUIT: goto CleanUp;
end;
SDL_Delay(10);
end;
CleanUp:
Terminated := True;
SDL_LockMutex(Mutex);
Flag := True;
SDL_SignalCondition(Condition);
SDL_UnlockMutex(Mutex);
SDL_WaitThread(Thread, nil);
SDL_DestroyCondition (Condition);
SDL_DestroyMutex (Mutex);
SDL_DestroyWindow (Window);
SDL_Quit();
end.
In short, the worker thread prints a message in the console every 500ms, but if you press the Space, it will wake up before the timeout is reached and print extra message in the console. Sample output:
While not directly spelled out, the documentation sample looks like a standardr worker thread. It waits until it’s told to do some work, using a condition variable and SDL_WaitConditionTimeout() to put the thread to sleep instead of busy waiting.
This does not loop until the condition variable has been signaled. SDL_WaitConditionTimeout() returns false (0) if the timeout has been reached, and in your “fix” it then exits the wait loop and goes about doing whatever work it was supposed to be waiting to do.
The flag is there to keep from exiting the wait loop in case of a spurious wake up.
On the other hand, if you wanted to wait until the condition variable had been signaled OR the timeout had been reached, that’s when you’d do something like what you posted
Ok, now everything is clear. I suspected that the thread might wake up too early and hence the loop to put it to sleep again. And yes, that’s what I mean, to recognize if a variable is signaled or a timeout has been reached, so your proposal is accurate. Thank you!
But that’s a bit misleading, since this example doesn’t really show a sensible use of the SDL_WaitConditionTimeout function, since the timeout is not taken into account at all, and the thread waits forever for the condition variable to be signaled.
IMO it would be a good idea to change this example to show how to use this function and check if the timeout has been reached. Otherwise this example makes no sense, because it does exactly the same thing as SDL_WaitCondition.
Another tip: avoid WriteLn, this is not thread-fixed. Use SDL_Log instead, it is persistent.
In your example, WriteLn works, but as soon as you use SDL_CreateThreadRuntime twice, it fails with a “runtime error 101”.
if not Terminated then
begin
if Signaled then
SDL_Log(' %5d Extra work.', SDL_GetTicks);
SDL_Log('%5d Periodic work.', SDL_GetTicks);
end;
It is on all platforms that support threads as the corresponding file variables are declared as threadvar. What is not guaranteed (on any platform) is the order of the output and that you might not get mixed output.
I use WriteLn mainly because it is easy and convenient to use, it is part of the RTL (SDL is not needed), and it prints what I want, without any prefixes or filtering.
I use Windows 10 and have never received this error, even with dozens of threads printing data to the console, as fast as possible. So I don’t know where you got this error from, especially since runtime error 101 is about the disk being full and unable to write data:
101 Disk write error
Reported when the disk is full, and you’re trying to write to it.
I use Linux and it crashes. I also noticed that 101 means disk full. I also managed to get rid of this error with Linux Pure and multithreading.
I put this in and the error went away.