Atomics¶
This primer illustrates Chapel’s atomic variables. For more information on Chapel’s atomics, see the Atomic Variables section of the language specification.
The atomic
keyword is a type qualifiers that can be applied to
the following Chapel primitive types to declare atomic variables:
bool
int
(all supported sizes)uint
(all supported sizes)real
(all supported sizes)
Atomic variables support a limited number of operations that are currently implemented as methods.
Atomic variables can only be used or assigned via the read()
and
write()
methods, respectively.
config const n = 31;
const R = 1..n;
var x: atomic int;
x.write(n);
if x.read() != n then
halt("Error: x (", x.read(), ") != n (", n, ")");
All atomic types support atomic exchange()
, and compareAndSwap()
The exchange()
method atomically swaps the old value with the given
argument and returns the old value. The compareAndSwap()
method only
perform the swap if the current value is equal to the first argument,
returning true
if the exchange was performed.
In the following example, n
parallel tasks are created via a
coforall
statement. Each task tries to set the current value of x
to id-1
, but only one will succeed.
var numFound: atomic int;
coforall id in R {
numFound.add(x.compareAndSwap(n, id-1));
}
if numFound.read() != 1 then
halt("Error: numFound != 1 (", numFound.read(), ")");
if x.read() == n then
halt("Error: x == n");
As a convenience, atomic bools
also support testAndSet()
and
clear()
. The testAndSet()
method atomically reads the value of
the variable, sets it to true
, then returns the original value. The
clear()
method atomically sets the value to false
.
In the following example, we create n
tasks and each task calls
testAndSet()
on the atomic variable named flag
and saves the
result in a unique element of the result
array.
var flag: atomic bool;
var result: [R] bool;
coforall r in result do
r = flag.testAndSet();
When the coforall
is complete, only one of the above testAndSet()
calls should have returned false
.
var found = false;
for r in result {
if !r then
if found then
halt("Error: found multiple times!");
else
found = true;
}
if !found then
halt("Error: not found!");
flag.clear();
The integral and real
atomic types also support the following atomic
fetch and non-fetching operations:
fetchAdd()
andadd()
fetchSub()
andsub()
fetchOr()
andor()
(bit-wise) (integral types only)fetchAnd()
andand()
(bit-wise) (integral types only)fetchXor()
andxor()
(bit-wise) (integral types only)
Each of the above atomically reads the variable, stores the result
of the operation (+
, -
, |
, &
, ^
) using the value and the
method argument, then, for the fetchOps functions, returns the original
value.
In the following example, we create n
tasks to atomically increment
the atomic variable a
with the square of the task’s given id
.
var a: atomic int;
coforall id in R do a.add(id*id);
The sum of this finite series should be n(n+1)*(2n+1)/6
var expected = n*(n+1)*(2*n+1)/6;
if a.read() != expected then
halt("Error: a=", a.read(), " (should be ", expected, ")");
In the following example, we create n
tasks to atomically increment
the atomic variable a
with the square of the task’s given id
. The
sum of this finite series is n(n+1)*(2n+1)/6
. If the returned value
is the expected value less id*id
(last task to arrive), check the
results.
a.write(0);
const sumOfSq = n*(n+1)*(2*n+1)/6;
coforall id in R {
const mySq = id*id;
const last = a.fetchAdd(mySq);
if sumOfSq-mySq == last {
const t = a.read();
if t != n*(n+1)*(2*n+1)/6 then
halt("Error: a=", t, " (should be ", sumOfSq, ") id=", id);
}
}