Semaphore, Barrier and Latch
Coordination between threads: Semaphore bounds how many threads do
something at once, Barrier makes a group of threads meet before any
goes on, and Latch lets threads wait until a count of events has
happened.
Source: runtime/async/coordination.pep
Semaphore
[Shared] struct Semaphore
A count of permits that threads take and give back, like Java’s
Semaphore or C++20 std::counting_semaphore: at most permits threads
hold one at a time.
acquire() waits until a permit is free and takes it; release() gives
one back. The usual form is a with block, which gives the permit back
on every path out: with (semaphore.permit()) { ... }.
A Semaphore is [Shared]: keep it in a module-scope variable or pass it
to threads. Permits are not tied to a thread: any thread may release a
permit that another acquired, and release() may raise the count above
where it started.
In a task (see run), waiting for a permit parks the task, not its worker
thread.
Example
import async;
# At most two downloads at once.
const downloads = async.Semaphore(2);
download(url: String) {
with (downloads.permit()) {
# ... fetch url ...
}
}
Constructor
Semaphore()
construct(permits: ulong)
Creates a Semaphore with permits free permits.
Parameters
permits— how many permits are free at first; 0 makes everyacquire()wait for arelease()
Methods
acquire
acquire()
Waits until a permit is free, and takes it.
Give it back with release(), or use permit() in a with block,
which gives it back for you.
try_acquire
try_acquire(): bool
Takes a permit only if one is free right now, without waiting.
Returns
- true if the caller now holds a permit, false if none was free
release
release()
Gives a permit back, waking one thread waiting in acquire() if any.
permit
permit(): SemaphorePermit
Waits until a permit is free, takes it, and returns a guard that gives
it back when destroyed: at the end of with (semaphore.permit()) { ... }
by any path.
Returns
- the guard holding the permit
Example
with (connections.permit()) {
serve(client);
} # the permit is given back here
SemaphorePermit
struct SemaphorePermit
A permit taken from a Semaphore, given back when the guard is destroyed.
Get one from Semaphore.permit() in a with header.
Example
with (var permit = pool.permit()) {
# holds one of the pool's permits
}
Constructor
SemaphorePermit()
construct(semaphore: Semaphore)
Waits for a permit from semaphore and holds it; the same as
semaphore.permit().
Parameters
semaphore— the Semaphore to take a permit from
Methods
enter
enter(): SemaphorePermit
Returns this guard, for the with header.
Returns
- this permit
exit
exit()
Gives the permit back if it is still held. The destructor calls it; calling it again does nothing.
Barrier
[Shared] struct Barrier
A meeting point for a fixed number of threads, like Rust’s
std::sync::Barrier: each thread that calls wait() sleeps until count
threads have called it, then all of them go on together.
Exactly one thread of each group, the leader, gets true from wait(), so
it can do the work that must happen once per round. The barrier then
resets, and the next count calls make the next round.
A Barrier is [Shared]. In a task (see run), wait() parks the task,
not its worker thread; the other tasks of a round must then run on other
workers or later on the same one.
Example
import async;
const step_done = async.Barrier(4);
work(id: long) {
var step = 0;
while (step < 10) {
# ... this thread's share of the step ...
if (step_done.wait()) {
print($"step {step} done\n"); # once per step
}
step = step + 1;
}
}
Constructor
Barrier()
construct(count: ulong)
Creates a barrier for groups of count threads.
Parameters
count— how many threads each round waits for; 0 counts as 1, so everywait()returns at once as the leader
Methods
wait
wait(): bool
Waits until count threads, this one included, have called wait()
in this round.
Returns
- true on exactly one thread of each round, the last to arrive; false on the others
Latch
[Shared] struct Latch
A one-shot countdown, like C++20 std::latch: threads wait until
count_down() has been called count times. Once open it stays open.
Unlike a Barrier, the threads that count down need not wait, and the
threads that wait need not count down: a typical use is a thread that
starts workers and waits for each to report that it is ready.
A Latch is [Shared]. In a task (see run), wait() parks the task, not
its worker thread.
Example
import async;
const ready = async.Latch(3);
worker() {
# ... set up ...
ready.count_down();
# ... work ...
}
main(): int {
var threads = Array<async.Thread>();
var i = 0;
while (i < 3) {
threads.append(async.Thread.spawn(worker));
i = i + 1;
}
ready.wait(); # every worker has set up
for (var t in threads) {
t.join();
}
return 0;
}
Constructor
Latch()
construct(count: ulong)
Creates a latch that opens after count calls to count_down().
Parameters
count— how many calls open it; 0 makes a latch that is open from the start
Methods
count_down
count_down()
Counts one event, and opens the latch, waking every waiter, if it was the last one.
Panics
- if the latch is already open
wait
wait()
Waits until the latch is open. Returns at once if it already is.
is_ready
is_ready(): bool
Reports whether the latch is open, without waiting.
Returns
- true once
count_down()has been calledcounttimes