Some example of mutex usage:
register_callback(cb){
mutex_lock(&mtx);
if (!evnt_fired){
callbklist[num_callbacks++] = cb;
mutex_unlock(&mtx);
}else{
mutex_unlock(&mtx);
cb();
}
return;
}
Above function takes the mutex, check if the flag of evnt_fired not set, then adding the callback routine to the list and unlock mutex ;else unlock the mutex and call the callback()
void event_fired(){
mutex_lock(&mtx);
evnt_fired=TRUE;
mutex_unlock(&mtx);
for (int ii=0; ii< num_callbacks; ii++){
callback_list[ii]();
}
Above function takes the mutex , sets the flag to TRUE , unlocks the mutex , and then calls each callback function.
This can be modified as:
void event_fired(){
evnt_fired=TRUE;
mutex_lock(&mtx);
mutex_unlock(&mtx);
for (int ii=0; ii< num_callbacks; ii++){
callback_list[ii]();
}
Here we are taking mutex lock after setting the flag. The code still works properly if the flag is atomic variable. So here there is no code in between the mutex lock and unlock, still no issue as protection is added at the time of modification of callback_list[]. This mutex lock-unlock statements work here as a barrier.
A barrier is a synchronization mechanism that ensures all participating threads reach a certain point in their execution before any of them proceed. This is useful when you want threads to perform tasks in phases and ensure that no thread starts the next phase until all threads have completed the current one.
Key Concepts of Barriers
- Synchronization Point: All threads must reach the barrier before any can continue. This is useful for coordinating tasks that must be completed collectively at the same stage of execution.
- Thread Coordination: Barriers are typically used in parallel computing to split tasks into phases, where each phase must be completed by all threads before moving on to the next.
In this context, this lock-unlock ensures that register_callback() is completed adding the cb to the callback_list.
Suppose the code is modified to add mutex lock-unlock before setting the flag as:
void event_fired(){
mutex_lock(&mtx);
mutex_unlock(&mtx);
evnt_fired=TRUE;
for (int ii=0; ii< num_callbacks; ii++){
callback_list[ii]();
}
In this case, there is a race condition problem that there can be evnt_fired as FALSE in register_callback, but became TRUE in this function and callback_list may have unpredicted data.
pthread library also provides the barrier APIs. Example code is:
- Initialization:
pthread_barrier_init(&barrier, NULL, NUM_THREADS);
initializes the barrier to synchronizeNUM_THREADS
threads. - Thread Work: Each thread performs some work in the first phase and then waits at the barrier with
pthread_barrier_wait(&barrier);
. - Barrier Synchronization: When all threads reach the barrier, they are unblocked and proceed to the next phase.
- Cleanup:
pthread_barrier_destroy(&barrier);
cleans up the barrier resources.
Advantages and Use Cases
- Phase-based Computation: Useful in algorithms where threads must complete certain steps before proceeding to the next (e.g., parallel sorting, matrix multiplication).
- Load Balancing: Ensures all threads synchronize at key points, which can help in load balancing and resource management.