_Accept
The _Accept
statement is a synchronization construct that precisely controls which monitor member function can be executed next, based on the state of the shared resource.
The _Accept
statement indicates which call is acceptable given the current state of the Monitor.
If the buffer is full, the only acceptable call is to remove, which frees a buffer slot; all calls to insert, including the insert call executing the _Accept
statement, are held (blocked) until a call to remove occurs. Similarly, if the buffer is empty, the only acceptable call is to insert, which fills a buffer slot; all calls to remove, including the remove call executing the _Accept statement, are held (blocked) until a call to insert occurs.
Cooperation
Unlike the conditional critical-region, when a task in a monitor knows it cannot execute, it does not passively wait for its condition to become true, it actively indicates that its condition can only become true after a call occurs to a particular mutex member, which is a very precise statement about what should happen next in the monitor, and hence, a very precise form of cooperation.
Notice
- The
_Accept
statement does not specify the next task to enter the monitor, only the next mutex member to execute when calls occur.
- Indirectly selected a task through the mutex member it calls
- The
_Accept
statement allows tasks to use the monitor in non-FIFO order, like the conditional of the conditional critical-region.
- That is, the entry queue may have several producer tasks blocked on it because the buffer is full, but when a consumer arrives its call to remove is accepted ahead of the waiting producers. In the case where a consumer is already waiting on the entry queue when a producer accepts remove, the waiting consumer is immediately removed from the entry queue (regardless of its position in the queue) and allowed direct entry to the monitor. Hence, when the buffer is full, a waiting consumer is processed immediately or a delay occurs until a consumer calls the monitor.
- Therefore, the monitor implementation for _Accept must check if there is any task waiting on the entry queue for the accepted member, which may require a linear search of the entry queue
Because linear search is slow, the monitor implementation usually puts a calling task on two queues: the entry queue, in order of arrival, and a mutex queue for the called member, also in order of arrival.
- Each task appears on two queues (duplicates are shaded)
- mutex queues are optional; only enhancing the efficiency of the implementation over searching the entry queue for an accepted caller.
The task executing the _Accept
blocks, which implicitly releases mutual exclusion on the monitor so a call can proceed to the accepted mutex member.
Where does the accepting task block?
- In the case of a condition critical-region, a task executing an await statement with a false condition blocks on the shared-variable’s implicit conditional queue.
- In the case of a task executing a
_Accept
statement, the task blocks on an implicit stack associated with the monitor, called the acceptor/signalled stack
- It’s a stack because accepts can be nested! To manage the blocked accepting tasks.
- In the case of the accept call sequence, the calls are performed by different tasks.
Quick Summary (External Scheduling)
- Role of
_Accept
in Synchronization
- The
_Acccept
statement specifies which call is permissible given the state of the monitor. For example - If a buffer is full, onlyremove
calls can proceed, blockinginsert
calls until there is space. - If the buffer is empty, onlyinsert
calls are permitted, andremove
calls are blocked until a buffer slot is filled.- Active Cooperation
- Unlike conditional critical regions, where tasks passively wait, tasks in a monitor using
_Accept
actively indicate their conditions and what must change to proceed, making it a precise synchronization mechanism.- Task Selection and Execution
- The
_Accept
statement does not directly choose which task will proceed; rather, it specifies the next mutex function that can execute when a call is made.- This results in a non-FIFO execution order, as demonstrated by producer-consumer scenarios where a consumer may “jump the queue” of waiting producers if the buffer is full.
- Efficiency Considerations
- Since linear searches of entry queues can be slow, a monitor implementation may use two queues:
- An entry queue (tasks in order of arrival)
- A mutex queue (asks waiting for a specific member function), which enhances efficiency.
- Tasks appear in both queues to optimize the acceptance process.
- Blocking Mechanism
- When a task executes
_Accept
, it releases mutual exclusion on the monitor, allowing another task to proceed. The accepting task itself block on a specialized data structure called the acceptor/signalled stack (a sack structure to support nested accepts).- Code Organization
- The mutually recursive nature of
insert
andremove
calls necessitates defining their bodies outside the monitor type due to C++‘s definition-before-use rule.