Thread synchronization

Scheduler needs to react as soon as possible after each thread finishes processing its part of data. As it is neither possible to wait for signal on multiple pthread_conds, nor efficient or safe (as it creates a risk of race condition) to iterate over mutexes, a message queue is used.

After given thread finishes processing its data, it pushes its thread ID to the queue. The queue is implemented with a blocking read: if queue is empty, it will block the read until a new message has been added to it. Thanks to that the Scheduler can continuously pull new messages from the queue without wasting resources.

Each thread stops after processing a batch of data, so it is up to the Scheduler to decide whether new data is ready to be processed or not, and it is the Scheduler that needs to notify the thread about that.

Abstraction of thread signaling

Most of threads managed by Scheduler have these things in common:

they need to be told when new data is ready to be processed,

they need to push message to the queue when they have finished processing data,

it needs to be possible to check whether thread is currently processing some data.

These things were abstracted away in SignaledWorkerThread class. To turn any process into thread that supports this signaling, a function with a signature void process_function(void *) must be created and passed to SignaledWorkerThread::init() method together with an argument that should be passed to this function, a thread ID and a message queue.

Each time SignaledWorkerThread::resume_thread() is called, the process_function passed to init() will be called. SignaledWorkerThread::is_suspended() will return false until process_function returns. When it returns, the thread ID is pushed to message queue and the thread suspends until resume_thread is called again.