Thread Synchrony (but not the std.Thread.Barrier kind)

Hello all,

Disclaimer:
This topic is more of a Help one, but I still read it as a challenge, cuz this has me scratching my head. Before I actually lay down the basis, I should just give a disclaimer that even though I know how bad AI is when it comes to coding. I use it as a learning tool to understand how the data types and functionalities work in Zig. So, at any point you feel like I am under the wrong impression when it comes to a functionality or something, pls correct me (aggressively if u must).

The Actual Challenge:
The Challenge is to create a class (struct) of threads that are synchronized.

  • They must all have a concurrent start of execution in each cycle of their while loop.
  • The start should happen irrespective of the completion status of every thread.
  • If the thread reaches the end of the cycle too late, it must skip the frame altogether.
  • (This one I can excuse for now) the threads must be able to run at different fps, be that frame rate a multiple of the smallest fps among the threads.

My Approach:

  • At first, I tried using a single gate std.Thread.ResetEvent as a static variable that was shared across all the threads.
  • all threads waited at the end of their cycles at gate.wait() which was set() and reset() inside the main thread that ran on a sleep-optimized timer, which used
    sleep(frame_time - (now - last_frame_start)) logic

Problems:

  • Even though I provided a safe enough window for threads to pass, a few hundred microseconds between the set and reset calls, the program failed.
  • The Reset event is not very safe when it comes to more than two threads.
  • So I thought mutex, but unfortunately, they need a cond var for each one I use, and that might create latency if I have to invoke the cond vars in the main thread consecutively.

So here I am…

I wonder what you mean by a synchronized start. On a typical system there are no primitives for thread synchronization that guarantee a start time for a thread. You normally also do not specify if multiple treads run at the same time at all, it’s the OS that decides this based on your hardware. What threading gives you are independent control flows, that can be interrupted by the os in order to switch to a different control flow to drive this forward. If you have multiple cpus the os might decide to do this for multiple of your threads.

If you still want to implement this, you could use semaphores for a somewhat synchronized start, it can let multiple treads pass,

But you would need more synchronization to avoid a thread from entering multiple times at the same iteration. Like an atomic cycle counter or a second semaphore, so the thread only takes the semaphore if it did not already in a previous iteration. If you use two semaphores for even/odd cycles you can step the iterations forward synchronized, For different frame rates you probably want different semaphores.

I mean it as if I have a program with 3 thread it is highly unlikely that they will ever execute their tasks in the same interval based on the load the task demands that frame.

But I might know that each task is definitely being completed under 144Hz frame_time.

And so even though the conventional approach is the use of mutexs (or mutices?), blocking the shared resources so the data isnt torn. I wished to employ a system with a double buffer so that any write statement is completed in one frame and read in the next by the other thread.

And, since they have a sync start, that is a confirmation that prev flush of data was a success so I shouldn’t need to employ extra mutecies or other Futex wrappers when accessing shared storage in a frame.

basically doing something too elaborate to save a pinch of time and performance. It’s not an absolute requirement, but I would still like to see if it’s possible.

Is it something like this that you have in mind? Reading your description, I can only think of a thread barrier.

pub fn threadFn(shared: *Shared) void{
  while(true){
    shared.barrier.reach();
    unconditionalWork();
    const now = std.Io.Clock.real.now(shared.io) catch unreachable;
    if(now - shared.start_time < time_limit){
      conditionalWork();
    } 
  }
}

I moved this to “Explain” because “Challenge” is a weird category. See how there are votes, and you can comment on every post?

It exists for a specific kind of thread, and I don’t think you intended this to be that kind of thread, but rather the more ordinary kind. I know these things are not especially well explained.

However, moving it to Explain does not seem to have changed what “kind of thread” it is, for mysterious Discourse reasons. If there’s a way to do that as well, I’m not seeing it.