Hold Statement :Hold tkns
Whenever more than one thread tries to access the same piece of data or shared resource at the same time, you need some type of synchronisation to control access to that data. This is provided by :Hold
.
:Hold
provides a mechanism to control thread entry into a critical section of code. tkns
must be a simple character vector or scalar, or a vector of character vectors. tkns
represents a set of "tokens", all of which must be acquired before the thread can continue into the control structure. :Hold
is analogous to the component file system ⎕FHOLD
which is used to synchronise access between processes. See also File Hold.
Within the whole active workspace, a token with a particular value may be held only once. If the hold succeeds, the current thread acquires the tokens and execution continues with the first phrase in the control structure. On exit from the structure, the tokens are released for use by other threads. If the hold fails, because one or more of the tokens is already in use:
- If there is no
:Else
clause in the control structure, execution of the thread is blocked until the requested tokens become available. - Otherwise, acquisition of the tokens is abandoned and execution resumed immediately at the first phrase in the
:Else
clause.
tkns
can be either a single token:
'a'
'Red'
'#.Util'
''
'Program Files'
… or a number of tokens:
'red' 'green' 'blue'
'doe' 'a' 'deer'
,¨'abc'
↓⎕nl 9
Pre-processing removes trailing blanks from each token before comparison, so that, for example, the following two statements are equivalent:
:Hold 'Red' 'Green'
:Hold ↓2 5⍴'Red Green'
Unlike ⎕FHOLD
, a thread does not release all existing tokens before attempting to acquire new ones. This enables the nesting of holds, which can be useful when multiple threads are concurrently updating parts of a complex data structure.
In the following example, a thread updates a critical structure in a child namespace, and then updates a structure in its parent space. The holds will allow all "sibling" namespaces to update concurrently, but will constrain updates to the parent structure to be executed one at a time.
:Hold ⎕cs'' ⍝ Hold child space
... ⍝ Update child space
:Hold ##.⎕cs'' ⍝ Hold parent space
... ⍝ Update Parent space
:EndHold
...
:EndHold
However, with the nesting of holds comes the possibility of a "deadlock". For example, consider the two threads:
Thread 1 | Thread 2 |
---|---|
apl :Hold 'red' ... :Hold 'green' ... :EndHold :EndHold |
apl :Hold 'green' ... :Hold 'red' ... :EndHold :EndHold |
In this case if both threads succeed in acquiring their first hold, they will both block waiting for the other to release its token.
If this deadlock situation is detected acquisition of the tokens is abandoned. Then:
- If there is an
:Else
clause in the control structure, execution jumps to the:Else
clause. - Otherwise, APL issues an error
(1008) DEADLOCK
.
You can avoid deadlock by ensuring that threads always attempt to acquire tokens in the same chronological order, and that threads never attempt to acquire tokens that they already own.
Note that token acquisition for any particular :Hold
is atomic, that is, either all of the tokens or none of them are acquired. The following example cannot deadlock:
Thread 1 | Thread 2 |
---|---|
apl :Hold 'red' ... :Hold 'green' ... :EndHold :EndHold |
apl :Hold 'green' 'red' ... :EndHold |
Examples
:Hold
could be used for example, during the update of a complex data structure that might take several lines of code. In this case, an appropriate value for the token would be the name of the data structure variable itself, although this is just a programming convention: the interpreter does not associate the token value with the data variable.
:Hold'Struct'
... ⍝ Update Struct
Struct ← ...
:EndHold
The next example guarantees exclusive use of the current namespace:
:Hold ⎕CS'' ⍝ Hold current space
...
:EndHold
The following example shows code that holds two positions in a vector while the contents are exchanged.
:Hold ⍕¨to fm
:If >/vec[fm to]
vec[fm to]←vec[to fm]
:End
:End
Between obtaining the next available file tie number and using it:
:Hold '⎕FNUMS'
tie←1+⌈/0,⎕FNUMS
fname ⎕FSTIE tie
:End
The above hold is not necessary if the code is combined into a single line:
fname ⎕FSTIE tie←1+⌈/0,⎕FNUMS
or,
tie←fname ⎕FSTIE 0
Note that :Hold
, like its component file system counterpart ⎕FHOLD
, is a device to enable co-operating threads to synchronise their operation.
:Hold
does not prevent threads from updating the same data structures concurrently, it prevents threads only from :Hold
ing the same tokens.
High-Priority Callbacks
:Hold
with a non-zero number of tokens is not permitted in a high-priority callback and an attempt to use it will cause the error:
DOMAIN ERROR: Cannot :Hold within high priority callback