View syncs.chpl on GitHub

This primer illustrates Chapel’s sync variables.

sync is a type qualifier that can be applied to the Chapel primitive types nothing, bool, int, uint, real, imag, complex, range, bytes, and string; to enumerated types; and to class types and record types.

Sync variables have an associated state that is either full or empty. If a sync variable is declared with an initializing expression, its state is set to full and its value is that of the expression. Without an initializing expression, a sync variable’s state is set to empty and its value is the default value for the base type.

config const n = 7;

var si: sync int=1;  // state = full, value = 1
var sb: sync bool; // state = empty, value = false

Because of their full/empty state, reads and writes to sync variables must be done with methods to make it clear how the full/empty state is treated in the operation. If a sync variable is not in the correct state for a given operation, the current task blocks until it is.

Here are the methods defined on sync variables:

The writeEF() method blocks until the state is empty and then assigns the value argument to the variable and then sets the state to full.


The readFE() method blocks until the state is full and then reads the value of the variable, sets the state to empty, and then returns the value.

var si2 = si.readFE();
writeln("si2 ", si2);

The readFF() method blocks until the state is full and then reads the value of the variable and returns the value. The state remains full.

var sb2 = sb.readFF();
writeln("sb2 ", sb2);

The following creates a new task via a begin statement and declares a variable si4 that is initialized using si. The initialization statement will block until si is full. The last statement in the begin block sets done to full.

var done: sync bool;
writeln("Launching new task");
begin {
  var si4 = si.readFE(); // This statement will block until si is full
  writeln("New task unblocked, si4=", si4);

Recall that execution proceeds immediately following the begin statement after task creation.

writeln("After launching new task");

When si is written, its state will be set to full and the blocked task above will continue.


This next statement blocks until the last statement in the above begin completes.


Example: simple split-phase barrier for tasks

var count: sync int = n;    // counter which also serves as a lock
var release: sync bool;     // barrier release

coforall t in 1..n {
  var myc = count.readFE(); // read the count, grab the lock (state = empty)
  if myc!=1 {               // still waiting for others
    count.writeEF(myc-1);   // update the count, release the lock (state = full)
                            // we could do some work while waiting
    release.readFF();       // wait for everyone
  } else {                  // last one here
    release.writeEF(true);  // release everyone first (state = full)

Sync arguments are passed by reference. As a result, the state of the variable does not change.

writeln("now passing to f_withSyncIntFormal");

When passing a sync variable to a generic formal, whether with a ref intent or a default intent, the variable is passed by reference. The state of the variable does not change and sync operations are available.


Currently, sync variables cannot be written out directly. We need to extract the value, for example using readFE() or readFF().


Definitions of functions used above

proc f_withSyncIntFormal(x: sync int) {
  writeln("the value is: ", x.readFF());

proc f_withGenericDefaultIntentFormal(x) {
  writeln("the value is: ", x.readFF());

proc f_withGenericRefFormal(ref x) {
  writeln("the value is: ", x.readFF());