The functionality described in this section should only be used by experts, and even by those only with caution (especially the parts that relate to the memory model).
Not only is it possible to crash or hang the GAP kernel, it can happen in ways that are very difficult to reproduce, leading to software defects that are discovered only long after deployment of a package and then become difficult to correct.
The performance benefit of using these primitives is generally minimal; while concurrency can induce some overhead, the benefit from micromanaging concurrency in an interpreted language such as GAP is likely to be small.
These low-level primitives exist primarily for the benefit of kernel programmers; it allows them to prototype new kernel functionality in GAP before implementing it in C.
The LOCK
(11.1-1) operation combined with UNLOCK
(11.1-3) is a low-level interface for the functionality of the statement.
‣ LOCK ( [arg_1, ..., arg_n] ) | ( function ) |
LOCK
takes zero or more pairs of parameters, where each is either an object or a boolean value. If an argument is an object, the region containing it will be locked. If an argument is the boolean value false
, all subsequent locks will be read locks; if it is the boolean value true
, all subsequent locks will be write locks. If the first argument is not a boolean value, all locks until the first boolean value will be write locks.
Locks are managed internally as a stack of locked regions; LOCK
returns an integer indicating a pointer to the top of the stack; this integer is used later by the UNLOCK
(11.1-3) operation to unlock locks on the stack up to that position. If LOCK
should fail for some reason, it will return fail
.
Calling LOCK
with no parameters returns the current lock stack pointer.
‣ TRYLOCK ( [arg_1, ..., arg_n] ) | ( function ) |
TRYLOCK
works similarly to LOCK
(11.1-1). If it cannot acquire all region locks, it returns fail
and does not lock any regions. Otherwise, its semantics are identical to LOCK
(11.1-1).
‣ UNLOCK ( stackpos ) | ( function ) |
UNLOCK
unlocks all regions on the stack at stackpos or higher and sets the stack pointer to stackpos.
gap> l1 := ShareObj([1,2,3]);; gap> l2 := ShareObj([4,5,6]);; gap> p := LOCK(l1); 0 gap> LOCK(l2); 1 gap> UNLOCK(p); # unlock both RegionOf(l1) and RegionOf(l2) gap> LOCK(); # current stack pointer 0
HPC-GAP supports hash locks; internally, the kernel maintains a fixed size array of locks; objects are mapped to a lock via hash function. The hash function is based on the object reference, not its contents (except for short integers and finite field elements).
gap> l := [ 1, 2, 3];; gap> f := l -> Sum(l);; gap> HASH_LOCK(l); # lock 'l' gap> f(l); # do something with 'l' 6 gap> HASH_UNLOCK(l); # unlock 'l'
Hash locks should only be used for very short operations, since there is a chance that two concurrently locked objects map to the same hash value, leading to unnecessary contention.
Hash locks are unrelated to the locks used by the atomic
statements and the LOCK
(11.1-1) and UNLOCK
(11.1-3) primitives.
‣ HASH_LOCK ( obj ) | ( function ) |
HASH_LOCK
obtains the read-write lock for the hash value associated with obj
.
‣ HASH_UNLOCK ( obj ) | ( function ) |
HASH_UNLOCK
releases the read-write lock for the hash value associated with obj
.
‣ HASH_LOCK_SHARED ( obj ) | ( function ) |
HASH_LOCK_SHARED
obtains the read-only lock for the hash value associated with obj
.
‣ HASH_UNLOCK_SHARED ( obj ) | ( function ) |
HASH_UNLOCK_SHARED
releases the read-only lock for the hash value associated with obj
.
HPC-GAP allows migration of arbitrary objects to the public region. This functionality is potentially dangerous; for example, if two threads try resize a plain list simultaneously, this can result in memory corruption.
Accordingly, such data should never be accessed except through operations that protect accesses through locks, memory barriers, or other mechanisms.
‣ MAKE_PUBLIC ( obj ) | ( function ) |
MAKE_PUBLIC
makes obj and all its subobjects members of the public region.
‣ MAKE_PUBLIC_NORECURSE ( obj ) | ( function ) |
MAKE_PUBLIC_NORECURSE
makes obj, but not any of its subobjects members of the public region.
The memory models of some processors do no guarantee that read and writes reflect accesses to main memory in the same order in which the processor performed them; for example, code may write variable v1
first, and v2
second; but the cache line containing v2
is flushed to main memory first so that other processors see the change to v2
before the change to v1
.
Memory barriers can be used to prevent such counter-intuitive reordering of memory accesses.
‣ ORDERED_WRITE ( expr ) | ( function ) |
The ORDERED_WRITE
function guarantees that all writes that occur prior to its execution or during the evaluation of expr become visible to other processors before any of the code executed after.
Example:
gap> y:=0;; f := function() y := 1; return 2; end;; gap> x := ORDERED_WRITE(f()); 2
Here, the write barrier ensure that the assignment to y
that occurs during the call of f()
becomes visible to other processors before or at the same time as the assignment to x
.
This can also be done differently, with the same semantics:
gap> t := f();; # temporary variable gap> ORDERED_WRITE(0);; # dummy argument gap> x := t; 2
‣ ORDERED_READ ( expr ) | ( function ) |
Conversely, the ORDERED_READ
function ensures that reads that occur before its call or during the evaluation of expr are not reordered with respects to memory reads occurring after it.
There are two new functions to exchange a pair of objects.
‣ SWITCH_OBJ ( obj1, obj2 ) | ( function ) |
SWITCH_OBJ
exchanges its two arguments. All variables currently referencing obj1 will reference obj2 instead after the operation completes, and vice versa. Both objects stay within their previous regions.
gap> a := [ 1, 2, 3];; gap> b := [ 4, 5, 6];; gap> SWITCH_OBJ(a, b); gap> a; [ 4, 5, 6 ] gap> b; [ 1, 2, 3 ]
The function requires exclusive access to both objects, which may necessitate using an atomic statement, e.g.:
gap> a := ShareObj([ 1, 2, 3]);; gap> b := ShareObj([ 4, 5, 6]);; gap> atomic a, b do SWITCH_OBJ(a, b); od; gap> atomic readonly a do Display(a); od; [ 4, 5, 6 ] gap> atomic readonly b do Display(b); od; [ 1, 2, 3 ]
‣ FORCE_SWITCH_OBJ ( obj1, obj2 ) | ( function ) |
FORCE_SWITCH_OBJ
works like SWITCH_OBJ
(11.5-1), except that it can also exchange objects in the public region:
gap> a := ShareObj([ 1, 2, 3]);; gap> b := MakeImmutable([ 4, 5, 6]);; gap> atomic a do FORCE_SWITCH_OBJ(a, b); od; gap> a; [ 4, 5, 6 ]
This function should be used with extreme caution and only with public objects for which only the current thread has a reference. Otherwise, undefined behavior and crashes can result from other threads accessing the public object concurrently.
generated by GAPDoc2HTML