In atomicint§

See primary documentation in context for sub cas

multi cas(atomicint $target is rw, int $expected, int $value)
multi cas(atomicint $target is rw, Int() $expected, Int() $value)
multi cas(atomicint $target is rw, &operation)

Performs an atomic compare and swap of the native integer value in location $target. The first two forms have semantics like:

my int $seen = $target;
if $seen == $expected {
    $target = $value;
}
return $seen;

Except it is performed as a single hardware-supported atomic instruction, as if all memory access to $target were blocked while it took place. Therefore it is safe to attempt the operation from multiple threads without any other synchronization. For example:

my atomicint $master = 0;
await start {
    if cas($master, 0, 1) == 0 {
        say "Master!"
    }
} xx 4

Will reliably only ever print Master! one time, as only one of the threads will be successful in changing the 0 into a 1.

Both $expected and $value will be coerced to Int and unboxed if needed. An exception will be thrown if the value cannot be represented as a 64-bit integer. If the size of atomicint is only 32 bits then the values will be silently truncated to this size.

The third form, taking a code object, will first do an atomic fetch of the current value and invoke the code object with it. It will then try to do an atomic compare and swap of the target, using the value passed to the code object as $expected and the result of the code object as $value. If this fails, it will read the latest value, and retry, until a CAS operation succeeds. Therefore, an atomic multiply of an atomicint $i by 2 could be implemented as:

cas $i, -> int $current { $current * 2 }

If another thread changed the value while $current * 2 was being calculated then the block would be called again with the latest value for a further attempt, and this would be repeated until success.

In Scalar§

See primary documentation in context for sub cas

multi cas(Mu $target is rw, Mu \expected, Mu \value)
multi cas(Mu $target is rw, &operation)

Performs an atomic compare and swap of the value in the Scalar $target. The first form has semantics like:

my $seen = $target;
if $seen<> =:= $expected<> {
    $target = $value;
}
return $seen;

Except it is performed as a single hardware-supported atomic instruction, as if all memory access to $target were blocked while it took place. Therefore it is safe to attempt the operation from multiple threads without any other synchronization. Since it is a reference comparison, this operation is usually not sensible on value types.

For example:

constant NOT_STARTED = Any.new;
constant STARTED = Any.new;
my $master = NOT_STARTED;
await start {
    if cas($master, NOT_STARTED, STARTED) === NOT_STARTED {
        say "Master!"
    }
} xx 4

Will reliably only ever print Master! one time, as only one of the threads will be successful in changing the Scalar from NOT_STARTED to STARTED.

The second form, taking a code object, will first do an atomic fetch of the current value and invoke the code object with it. It will then try to do an atomic compare and swap of the target, using the value passed to the code object as expected and the result of the code object as value. If this fails, it will read the latest value, and retry, until a CAS operation succeeds.

Therefore, an item could be added to the head of a linked list in a lock free manner as follows:

class Node {
    has $.value;
    has Node $.next;
}
my Node $head = Node;
await start {
    for ^1000 -> $value {
        cas $head, -> $next { Node.new(:$value, :$next) }
    }
} xx 4;

This will reliably build up a linked list of 4000 items, with 4 nodes with each value ranging from 0 up to 999.

Note: Before Rakudo version 2020.12, $target, expected and value had an Any type constraint.