Module sync/waitgroup
Wait for a group of tasks to complete, similar to Go's sync.WaitGroup.
Example
{ WaitGroup } :: import "std/sync/waitgroup";
{ Thread } :: import "std/thread";
wg := WaitGroup.new();
wg.add(i32(3));
i := i32(0);
while(i < i32(3), {
t := Thread(unit).spawn((io) => {
// ... do work ...
wg.done();
});
i = (i + i32(1));
});
wg.wait(); // blocks until all 3 tasks call done()
Stability
unstable — the type has concurrent coverage
(tests/sync/waitgroup.test.yo) and the Go-derived names are settled,
but one behaviour is an unresolved divergence rather than a decision:
add CLAMPS the counter at zero, so one done() too many is silently
ignored where Go panics with "negative WaitGroup counter" — the one
place Go reliably catches a miscounted group.
plans/STD_API_STABILIZATION_FINDINGS.md item 10 records it as a gap.
Freezing needs that call made (clamp, panic, or a Result), because
whichever way it goes is observable to every caller. Note also that
this is not Go's value type: a WaitGroup here is an atomically
reference-counted handle, so copying one is correct and shares the
counter, where copying Go's is a bug.
Types
Synchronization primitive that blocks until a counter reaches zero. Uses atomic reference counting for safe cross-thread sharing.
The internal counter is an atomic so count() can read without
acquiring the mutex (matches Rust's mpsc query-method idiom). Writes
to the counter still happen under the mutex so the _count == 0
observation that triggers broadcast() is serialized with wait().
Fields
| Name | Type | Description |
|---|---|---|
_count | AtomicI32 | |
_mutex | Mutex(bool) | |
_cv | Cond |
impl(WaitGroup, ...)
new : (WaitGroup) fn() -> WaitGroupCreate an empty group — counter zero, no waiters. A wait() on it
returns immediately until something is added.
Returns: WaitGroup
add : (WaitGroup) fn(self : WaitGroup, delta : i32) -> unitAdd delta to the counter, waking every waiter if it reaches zero —
Go's WaitGroup.Add.
Call this BEFORE spawning the tasks it counts: a wait() that runs
while the counter is still zero returns immediately, and no later
add can bring it back. delta may be negative (done is exactly
add(-1)).
The counter is clamped at zero, not checked. A delta that would
take it below zero stores zero and broadcasts, so an extra done()
is silently absorbed rather than panicking as Go's does — see the
module's Stability note.
This briefly takes the group's internal mutex, so it blocks only
against another add or the bookkeeping of wait, never for the
length of the tasks themselves.
Parameters
| Name | Type | Notes |
|---|---|---|
self | WaitGroup | |
delta | i32 |
Returns: unit
done : (WaitGroup) fn(self : WaitGroup) -> unitMark one counted task finished — Go's WaitGroup.Done, and exactly
add(-1), including the clamp at zero.
Parameters
| Name | Type | Notes |
|---|---|---|
self | WaitGroup |
Returns: unit
wait : (WaitGroup) fn(self : WaitGroup) -> unitBlock until the counter reaches zero — Go's WaitGroup.Wait.
Returns immediately when the counter is already zero, which includes
the case of a group nothing was ever added to. Several threads may
wait at once; the zero is announced with a broadcast, so all of
them wake. The predicate is re-checked in a loop, so a spurious
condvar wake cannot end the wait early.
This is a BLOCKING (OS-thread) wait, not an io.await — it parks the
calling thread, so calling it on the event-loop thread stalls every
async task in the program. Use it from a thread that owns its own
work.
Parameters
| Name | Type | Notes |
|---|---|---|
self | WaitGroup |
Returns: unit
count : (WaitGroup) fn(self : WaitGroup) -> i32How many counted tasks are still outstanding, as a lock-free Acquire load — no mutex is taken.
Advisory only: another thread can change it between the load and the
next line, so branching on count() == 0 instead of calling wait()
is a race. It exists for diagnostics and progress reporting.
Parameters
| Name | Type | Notes |
|---|---|---|
self | WaitGroup |
Returns: i32