Goto Chapter: Top 1 2 3 4 5 6 7 8 9 10 11 Ind
 [Top of Book]  [Contents]   [Previous Chapter]   [Next Chapter] 

11 Low-level functionality
 11.1 Explicit lock and unlock primitives
 11.2 Hash locks
 11.3 Migration to the public region
 11.4 Memory barriers
 11.5 Object manipulation

11 Low-level functionality

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.

11.1 Explicit lock and unlock primitives

The LOCK (11.1-1) operation combined with UNLOCK (11.1-3) is a low-level interface for the functionality of the statement.

11.1-1 LOCK
‣ 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.

11.1-2 TRYLOCK
‣ 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).

11.1-3 UNLOCK
‣ 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);
gap> LOCK(l2);
gap> UNLOCK(p); # unlock both RegionOf(l1) and RegionOf(l2)
gap> LOCK(); # current stack pointer

11.2 Hash locks

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'
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.

11.2-1 HASH_LOCK
‣ 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.

11.3 Migration to the public region

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.

11.4 Memory barriers

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.


gap> y:=0;; f := function() y := 1; return 2; end;;
gap> x := ORDERED_WRITE(f());

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;

‣ 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.

11.5 Object manipulation

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.

 [Top of Book]  [Contents]   [Previous Chapter]   [Next Chapter] 
Goto Chapter: Top 1 2 3 4 5 6 7 8 9 10 11 Ind

generated by GAPDoc2HTML