Sync

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.

sb.writeEF(true);

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);
  done.writeEF(true);
}

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.

si.writeEF(n);

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

done.readFE();

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
    write(".");
    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)
    writeln("done");
  }
}

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

si.writeEF(4*n);
writeln("now passing to f_withSyncIntFormal");
f_withSyncIntFormal(si);

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.

f_withGenericDefaultIntentFormal(si);
f_withGenericDefaultIntentFormal(sb);
f_withGenericRefFormal(si);
f_withGenericRefFormal(sb);

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

writeln(si.readFF());
writeln(si.readFE());

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());
}