Algol 68 parallel clause
last modified November 13, 2026
A parallel clause runs the units of a collateral clause at the same time.
PAR (unit, unit) is the syntax, and the units share the variables
of the block that contains them. SEMA, LEVEL,
DOWN, and UP coordinate their access to those
variables.
This article describes the clause, the semaphores, and the discipline that a program needs when two units touch the same variable. The last section explains what this particular implementation does with the clause, which is why the examples are written the way they are.
Two units
Each comma-separated part of a PAR clause is a unit, and the
clause yields VOID. The units may assign to variables declared
before the clause, and the values are complete when the clause ends, so the
results are printed afterwards.
BEGIN
INT a, b;
PAR (
a := 2 + 2,
b := 3 * 3
);
print(("a = ", whole(a, 0), ", b = ", whole(b, 0), newline))
END
The two assignments are independent, and the print reads both variables after the clause has finished. Printing after the clause also keeps the output in one piece, which matters when the units really do run at the same time.
$ a68g units.a68 a = 4, b = 9
Collecting results in a row
A row is a natural place for the results of parallel work: every unit writes its own element, and no two units write the same place. The fourth unit reads a variable declared before the clause, which is allowed because reading a value that nobody writes is safe.
BEGIN
INT n := 5;
[1:4] INT results;
PAR (
results[1] := 1 * 1,
results[2] := 2 * 2,
results[3] := 3 * 3,
results[4] := n * n
);
FOR i TO UPB results DO
print((whole(results[i], 0), " "))
OD;
print((newline))
END
The four elements are printed in order after the clause, so the output does not depend on the order in which the units ran. A row of results is the usual shape of a program that splits a computation into independent parts.
$ a68g row.a68 1 4 9 25
A critical section
total +:= 1 is not a single action: the program reads the value,
adds one, and stores it back. Two units that interleave those steps can both
read the same value and one increment is lost. SEMA lock = LEVEL 1
creates a semaphore with one permit, and DOWN and UP
mark the critical section.
BEGIN
SEMA lock = LEVEL 1;
INT total := 0;
PAR (
FOR i TO 1000 DO DOWN lock; total +:= 1; UP lock OD,
FOR i TO 1000 DO DOWN lock; total +:= 1; UP lock OD
);
print(("total ", whole(total, 0), newline))
END
Only one unit can be between DOWN and UP at a time,
so the increments never overlap and the total is 2000 whatever the order of the
units. Without the semaphore the result may be smaller, and the size of the
loss changes from run to run. DOWN may only be used inside a
parallel clause; elsewhere it raises a runtime error.
$ a68g mutex.a68 total 2000
Waiting for a signal
LEVEL 0 starts the semaphore with no permits, so DOWN
waits until another unit calls UP. That is the shape of a
handshake: one unit produces a value and signals, and the other waits for the
signal and reads the value.
BEGIN
SEMA ready = LEVEL 0;
INT value, got;
PAR (
(value := 42; UP ready),
(DOWN ready; got := value)
);
print(("the consumer got ", whole(got, 0), newline))
END
The producer stores 42 and then signals; the consumer waits for the signal and copies the value. A unit that consists of several statements must be enclosed in parentheses, because the parts of a collateral clause are units and not serial clauses. The consumer cannot read the value before the producer has stored it, whichever unit starts first.
$ a68g handshake.a68 the consumer got 42
Counting the resources
LEVEL 2 gives the semaphore two permits, so two units may be inside
the critical section at once and a third has to wait. This is the way a program
limits the number of units that use a resource, such as a pool of connections
or a fixed amount of memory.
BEGIN
SEMA slots = LEVEL 2;
INT used := 0;
PAR (
FOR i TO 3 DO DOWN slots; used +:= 1; UP slots OD,
FOR i TO 3 DO DOWN slots; used +:= 1; UP slots OD
);
print(("used ", whole(used, 0), newline))
END
Six units pass through the section, three from each loop, and the count is 6. The permits do not change the total; they only decide how many units may be inside at one time, which is why the result is the same as with a mutex.
$ a68g slots.a68 used 6
What this build does
Algol 68 Genie accepts the parallel clause and the semaphores, and a program
that uses them runs correctly. The implementation used for this article,
version 2.8.4, executes the units of a clause one after another, however: two
units that each call sleep(2) take four seconds in total, and two
loops that print inside the units write their output one after the other
instead of interleaving it.
That behaviour explains why the examples print their results after the clause and keep the printed text in one piece. A program written for a build with real threads must work for any interleaving, and the semaphores are the tool for that; a build that runs the units in sequence simply never makes a unit wait. The examples above are written so that both kinds of build produce the same output.
Next steps
The procedures article explains the
PROC declarations that the units call, and
references and the heap shows how a unit can share
storage that outlives the clause. The loops
article covers the FOR clauses inside the units, and
environment and processes
measures the time that a computation takes.
Source
Revised Report on the Algorithmic Language Algol 68
In this article we have run units in a parallel clause, collected their results in a row, protected a critical section with a semaphore of level 1, waited for a signal from a semaphore of level 0, limited the number of units inside a section with a semaphore of level 2, and seen how this build runs the units.
Author
List all Algol 68 tutorials.