There are four built-in $ATTACH classes; all subtype from $LOCK. These classes all have an implicit locked status (unlocked, or locked by a particular thread) and a set of attached threads.
FUTURE{T} provides a handle to the result of a computation. It is an error to attach more than one thread to a future at a time.
ATTACH allows multiple threads to be attached, but does not allow return values.
Gates are powerful synchronization primitives which generalize fork/join, mailboxes, semaphores, and barrier synchronization. There is a typed GATE{T} that has a queue of values which must conform to 'T', and an unparameterized class GATE with only an integer counter.
In addition to thread attachment, these classes support the operations listed in the following tables See Operations supported by ATTACH, FUTURE{T}, GATE, and GATE{T}, See Operations supported by FUTURE{T} and GATE{T}, See Operations supported only by GATE{T}, and See Operations supported only by GATE. Some operations are exclusive: these lock the gate before proceeding and unlock it when the operation is complete. The exclusive operations also perform imports and exports significant to memory consistency (See Memory consistency).
Table 17-1. Operations supported by ATTACH, FUTURE{T}, GATE, and GATE{T}
Signature | Description | Exclusive? |
---|---|---|
create:SAME | Make a new unlocked synchronization object with an empty queue or zero counter and no attached threads. | N/A |
has_thread:BOOL | Returns true if there is an attached thread. | No |
threads:$LOCK | Returns a lock which blocks until lockable and there is some thread attached; then it is locked. Holding this lock does not prevent the completion of attached threads. | No |
no_threads:$LOCK | Returns a lock which blocks until lockable and there are no threads attached; then it is locked. Holding this lock does not prevent the attachment of threads by the holder. | No |
Table 17-2. Operations supported by FUTURE{T} and GATE{T}
Signature | Description | Exclusive? |
---|---|---|
get:T | Return head of queue without removing. Blocks until queue is not empty. | Yes |
empty:$LOCK | Returns a lock which blocks until lockable and the queue is empty; then it is locked. Holding this lock does not prevent the holder from making the queue become not empty. | No |
not_empty:$LOCK | Returns a lock which blocks until the gate is lockable and the gate's queue is not empty; then the gate is locked. Holding this lock does not prevent the holder from making the queue become empty. | No |
Table 17-3. Operations supported only by GATE{T}
Signature | Description | Exclusive? |
---|---|---|
size:INT | Returns number of elements in queue. | No |
set(T) | Replace head of queue with argument, or insert into queue if empty. | Yes |
enqueue(T) | Insert argument at tail of queue. | Yes |
dequeue:T | Block until queue is not empty, then remove and return head of queue. | Yes |
Table 17-4. Operations supported only by GATE
Signature | Description | Exclusive? |
---|---|---|
size:INT | Returns counter. | No |
get | Blocks until counter is nonzero. | Yes |
set | If counter is zero, set to one. | Yes |
enqueue | Increment counter. | Yes |
dequeue | Block until counter nonzero, then decrement. | Yes |
empty:$LOCK | Returns a lock which blocks until lockable and the counter is zero; then it is locked. Holding this lock does not prevent the holder from making the counter become nonzero. | No |
not_empty:$LOCK | Returns a lock which blocks until the gate is lockable and the gate's counter is nonzero; then the gate is locked. Holding this lock does not prevent the holder from making the counter become zero. | No |
Example 17-5. Using a future. The statement 'f :- compute' creates a new thread to do some computation; the current thread continues to execute. It blocks at 'f.get' if the result is not yet available.
-- Create a future of FLT f ::= #FUTURE{FLT}; f :- compute; ... result := f.get; |
Example 17-6. Obtaining the first result from several competing searches. Unlike a future, a gate may enqueue multiple values. When one of the threads succeeds, its result is enqueued in 'g'. If the results of the other two threads are not needed, additional code would be needed to prematurely halt the other threads.
g :- search(strategy1); g :- search(strategy2); g :- search(strategy3); ... result := g.dequeue; |