HPC-GAP provides a number of atomic object types. These can be accessed by multiple threads concurrently without requiring explicit synchronization, but can have non-deterministic behavior for complex operations. Atomic lists are fixed-size lists; they can be assigned to and read from like normal plain lists. Atomic records are atomic versions of plain records. Unlike plain records, though, it is not possible to delete elements from an atomic record. The primary use of atomic lists and records is to facilitate storing the result of idempotent operations and to support certain low-level operations. Atomic lists and records can have three different replacement policies: write-once, strict write-once, and rewritable. The replacement policy determines whether an already assigned element can be changed. The write-once policy allows elements to be assigned only once, with subsequent assignments being ignored; the strict write-once policy allows elements also to be assigned only once, but subsequent assignments will raise an error; the rewritable policy allows elements to be assigned different values repeatedly. The default for new atomic objects is to be rewritable. Thread-local records are variants of plain records that are replicated on a per-thread basis.
Atomic lists are created using the AtomicList
or FixedAtomicList
functions. After creation, they can be used exactly like any other list, except that atomic lists created with FixedAtomicList
cannot be resized. Their contents can also be read as normal plain lists using FromAtomicList
.
gap> a := AtomicList([1,2,4]); <atomic list of size 3> gap> WaitTask(RunTask(function() a[1] := a[1] + a[2]; end)); gap> a[1]; 3 gap> FromAtomicList(a); [ 3, 2, 4 ]
Because multiple threads can read and write the list concurrently without synchronization, the results of modifying the list may be non-deterministic. It is faster to write to fixed atomic lists than to a resizable atomic list.
‣ AtomicList ( list ) | ( function ) |
‣ AtomicList ( count, obj ) | ( function ) |
AtomicList
is used to create a new atomic list. It takes either a plain list as an argument, in which case it will create a new atomic list of the same size, populated by the same elements; or it takes a count and an object argument. In that case, it creates an atomic list with count elements, each set to the value of obj.
gap> al := AtomicList([3, 1, 4]); <atomic list of size 3> gap> al[3]; 4 gap> al := AtomicList(10, `"alpha"); <atomic list of size 10> gap> al[3]; "alpha" gap> WaitTask(RunTask(function() al[3] := `"beta"; end)); gap> al[3]; "beta"
‣ FixedAtomicList ( list ) | ( function ) |
‣ FixedAtomicList ( count, obj ) | ( function ) |
FixedAtomicList
works like AtomicList
(5.1-1) except that the resulting list cannot be resized.
‣ MakeFixedAtomicList ( list ) | ( function ) |
MakeFixedAtomicList
turns a resizable atomic list into a fixed atomic list.
gap> a := AtomicList([99]); <atomic list of size 1> gap> a[2] := 100; 100 gap> MakeFixedAtomicList(a); <fixed atomic list of size 2> gap> a[3] := 101; Error, Atomic List Element: <pos>=3 is an invalid index for <list>
‣ FromAtomicList ( atomic_list ) | ( function ) |
FromAtomicList
returns a plain list containing the same elements as atomic_list at the time of the call. Because other threads can write concurrently to that list, the result is not guaranteed to be deterministic.
gap> al := AtomicList([10, 20, 30]);; gap> WaitTask(RunTask(function() al[2] := 40; end)); gap> FromAtomicList(al); [ 10, 40, 30 ]
‣ ATOMIC_ADDITION ( atomic_list, index, value ) | ( function ) |
ATOMIC_ADDITION
is a low-level operation that atomically adds value to atomic_list[index]. It returns the value of atomic_list[index] after the addition has been performed.
gap> al := FixedAtomicList([4,5,6]);; gap> ATOMIC_ADDITION(al, 2, 7); 12 gap> FromAtomicList(al); [ 4, 12, 6 ]
‣ COMPARE_AND_SWAP ( atomic_list, index, old, new ) | ( function ) |
COMPARE_AND_SWAP
is an atomic operation. It atomically compares atomic_list[index] to old and, if they are identical, replaces the value (in the same atomic step) with new. It returns true if the replacement took place, false otherwise.
The primary use of COMPARE_AND_SWAP
is to implement certain concurrency primitives; most programmers will not need to use it.
Atomic records are atomic counterparts to plain records. They support assignment to individual record fields, and conversion to and from plain records.
Assignment semantics can be specified on a per-record basis if the assigned record field is already populated, allowing either an overwrite, keeping the existing value, or raising an error.
It is not possible to unbind atomic record elements.
Like plain records, atomic records can be converted to component objects using Objectify
.
‣ AtomicRecord ( capacity ) | ( function ) |
‣ AtomicRecord ( record ) | ( function ) |
AtomicRecord
is used to create a new atomic record. Its single optional argument is either a positive integer, denoting the intended capacity (i.e., number of elements to be held) of the record, in which case a new empty atomic record with that initial capacity will be created. Alternatively, the caller can provide a plain record with which to initially populate the atomic record.
gap> r := AtomicRecord(rec( x := 2 )); <atomic record 1/2 full> gap> r.y := 3; 3 gap> TaskResult(RunTask(function() return r.x + r.y; end)); 5 gap> [ r.x, r.y ]; [ 2, 3 ]
Any atomic record can later grow beyond its initial capacity. There is no limit to the number of elements it can hold other than available memory.
‣ FromAtomicRecord ( record ) | ( function ) |
FromAtomicRecord
returns a plain record copy of the atomic record record. This copy is shallow; elements of record will not also be copied.
gap> r := AtomicRecord();; gap> r.x := 1;; r.y := 2;; r.z := 3;; gap> FromAtomicRecord(r); rec( x := 1, y := 2, z := 3 )
There are three functions that set the replacement policy of an atomic object. All three can also be used with plain lists and records, in which case an atomic version of the list or record is first created. This allows programmers to elide AtomicList
(5.1-1) and AtomicRecord
(5.2-1) calls when the next step is to change their policy.
‣ MakeWriteOnceAtomic ( obj ) | ( function ) |
MakeWriteOnceAtomic
takes a list, record, atomic list, atomic record, atomic positional object, or atomic component object as its argument. If the argument is a non-atomic list or record, then the function first creates an atomic copy of the argument. The function then changes the replacement policy of the object to write-once: if an element of the object is already bound, then further attempts to assign to it will be ignored.
‣ MakeStrictWriteOnceAtomic ( obj ) | ( function ) |
MakeStrictWriteOnceAtomic
works like MakeWriteOnceAtomic
(5.3-1), except that the replacement policy is being changed to being strict write-once: if an element is already bound, then further attempts to assign to it will raise an error.
‣ MakeReadWriteAtomic ( obj ) | ( function ) |
MakeReadWriteAtomic
is the inverse of MakeWriteOnceAtomic
(5.3-1) and MakeStrictWriteOnceAtomic
(5.3-2) in that the replacement policy is being changed to being rewritable: Elements can be replaced even if they are already bound.
Thread-local records allow an easy way to have a separate copy of a record for each individual thread that is accessed by the same name in each thread.
gap> r := ThreadLocalRecord();; # create new thread-local record gap> r.x := 99;; gap> WaitThread( CreateThread( function() > r.x := 100; > Display(r.x); > end ) ); 100 gap> r.x; 99
As can be seen above, even though r.x
is overwritten in the second thread, it does not affect the value of r.x| in the first thread
‣ ThreadLocalRecord ( [defaults[, constructors]] ) | ( function ) |
ThreadLocalRecord
creates a new thread-local record. It accepts up to two initial arguments. The defaults argument is a record of default values with which each thread-local copy is initially populated (this happens on demand, so values are not actually read until needed). The second argument is a record of constructors; parameterless functions that return an initial value for the respective element. Constructors are evaluated only once per thread and only if the respective element is accessed without having previously been assigned a value.
gap> r := ThreadLocalRecord( rec(x := 99), > rec(y := function() return 101; end));; gap> r.x; 99 gap> r.y; 101 gap> TaskResult(RunTask(function() return r.x; end)); 99 gap> TaskResult(RunTask(function() return r.y; end)); 101
‣ SetTLDefault ( record, name, value ) | ( function ) |
SetTLDefault
can be used to set the default value of a record field after its creation. Here, record is a thread-local record, name is the string of the field name, and value is the value.
gap> r := ThreadLocalRecord();; gap> SetTLDefault(r, "x", 314); gap> r.x; 314 gap> TaskResult(RunTask(function() return r.x; end)); 314
‣ SetTLConstructor ( record, name, func ) | ( function ) |
SetTLConstructor
can be used to set the constructor of a thread-local record field after its creation, similar to SetTLDefault
(5.4-2).
gap> r := ThreadLocalRecord();; gap> SetTLConstructor(r, "x", function() return 2718; end); gap> r.x; 2718 gap> TaskResult(RunTask(function() return r.x; end)); 2718
generated by GAPDoc2HTML