Barrier
Another primitive synchronization technique used in parallel computing is barrier. Its role is to ensure that no thread can proceed beyond the point where it is placed until all threads managed by the barrier reach that point. An example of usage is when we distribute a computation across multiple threads and want to proceed with the program execution only when each thread has finished its own calculations.
In Pthreads, a barrier is represented by the pthread_barrier_t type and initialized using the following function:
int pthread_barrier_init(pthread_barrier_t *barrier, const pthread_barrierattr_t *attr, unsigned count);
The first parameter represents a reference to the barrier, the second parameter can be used to set barrier attributes (similar to mutex), and the last parameter denotes the number of threads that must reach the barrier for it to be released. This means that the barrier has an internal counter that counts the threads waiting for its release. When the counter reaches the number set during the barrier initialization, the threads can resume their parallel execution.
To deallocate a barrier, the following function is used:
int pthread_barrier_destroy(pthread_barrier_t *barrier);
Both functions return 0 if executed successfully or an error code otherwise.
To make a thread wait at a barrier (to "set a barrier" in code), the following function is used:
int pthread_barrier_wait(pthread_barrier_t *barrier);
The function above will return PTHREAD_BARRIER_SERIAL_THREAD for a single arbitrary thread from the barrier and 0 for all others. If the function encounters any errors, it will return an error code.
Every thread that needs to wait at the barrier will call the above function on the same variable of type pthread_barrier_t. If the number of threads calling pthread_barrier_wait is less than the parameter with which the barrier was initialized, it will never be unblocked.
A graphical representation of how a barrier works can be seen in the figure below, where we have a barrier initialized with 2. When thread T0 reaches the barrier, it gets blocked, as shown on the left side of the image. At some point in time, T1 will also reach the barrier, as seen in the center of the image. Only at that moment, the two threads can continue their individual execution, as shown on the right side of the image.