Barrier
A barrier is used to coordinate a group of tasks before and after performing a concurrent operation.
A single barrier provides a one-shot stop and start point, while multiple barriers can be combined into a cycle with multiple stop and start points and repeated interactions.
Remember peter talking about racing horses analogy in class!
Tip
A barrier is specifically for synchronization and cannot be used to build mutual exclusion.
Unlike previous synchronization locks, a barrier retains state about the events it manages, specifically the number of tasks blocked on the barrier.
Two kinds of barrier: threads equal group size (T == G) or threads greater than group size (T > G).
Since manipulation of this state requires mutual exclusion, most barriers use internal locking.
- Barrier is initialized to control 3 tasks and passed to each task by reference (not copied).
- Barrier blocks each task at call to block until all tasks have called block.
- Last task to call block does not block and releases other tasks (cooperation).
- Hence, all tasks leave together (synchronized) after arriving at the barrier.
Note
Must specify in advance total number of block operations before tasks are released.
Each barrier has the following basic structure:
- initialization: prepare work for the computation phase
- release: tell worker tasks (simultaneously) to perform their computation
- computation: each worker task performs a portion of the total computation, in arbitrary order and speed
- synchronization: the workers report completion of their computation
- summary: compile the individual computations into a final result
the basic structure of a worker task’s main routine in this approach is:
It is possible to eliminate both the coordinator task and the start synchronization simply by using cooperation among the worker tasks. The cooperation has the last worker synchronizing at the barrier, i.e., the task that ends the computation phase does the necessary summary work plus the initialization to begin the next computation, and finally, releases the other worker tasks. This action is done implicitly by the barrier, which knows when the last task arrives and uses that task’s thread to execute some cooperation code on behalf of all the tasks. Therefore, the last task does the work, but the code to do it is part of the barrier not the task’s main.
Hence, the basic structure of a worker task’s main routine is simplified to:
uBarrier
In C++, the type uBarrier
defines a barrier and a barrier is a special kind of coroutine called _Cormonitor
:
_Cormonitor
, to provide the necessary mutual exclusion and to allow code to be easily executed both before and after the N tasks synchronize on the barrier.
- Constructor of
uBarrier
:uBarrier(unsigned int total)
–this form specifies the total number of tasks participating in the synchronization. Appropriate values are 0. A 0 initialization value implies the barrier does not cause any task to wait, but each calling task runs member routine last.
total()
andwaiters()
return the total number of tasks participating in the synchronization and the total number of tasks currently waiting at the barrier.reset()
changes the total number of tasks participating in the synchronization; no tasks may be waiting in the barrier when the total is changed.block()
is called to synchronize withtotal
tasks; tasks block until anytotal
tasks have calledblock
.- virtual member
last()
is called by the last task to synchronize at the barrier.- can be replaced by subclassing from
uBarrier
to provide a specific action to be executed when synchronization is complete. - This capability is often used to reset a computation before releasing the tasks from the barrier to start the next computation. The default code for last is to resume the coroutine main.
- can be replaced by subclassing from