Module std::task
Concurrent tasks as an effect: code starts tasks only through a Tasks capability in its
row, so its caller's executor decides where and when they run. A Tasks capability is also a
std::time::Clock, so code written with Clock runs in a task unchanged. Use Pool in a
program and std::sim::world (deterministic, one seed one schedule) in a test.
A task waits for another through a key: Tasks::waker makes one, Tasks::park suspends
the task under it and Tasks::wake readies it again. A key parks one task, once; a wake
that comes before the park is kept, so that park returns at once and a race between the
two loses nothing. std::sync's Notify, Watch and Channel are built on them.
Every executor of std is shared over a portable state and may move a task to another
thread, so a bare with Tasks means Tasks portable migrates cross, the bound under which a
function runs under each of them; a bound written in the row replaces it.
Source: std/task.weir; committed interface: std/task.iface. Index
- effect
Tasks: Starting tasks, waiting on keys, and the clock they sleep on: what an executor handles. - handler
Pool: The work-stealing executor, for a program's tasks.
effect Tasks
pub effect Tasks : Clock default portable migrates cross {
fn spawn(f: held fn());
fn stop();
once fn yield_now();
once fn preempt();
fn waker() -> Waker;
once fn park(w: Waker);
fn wake(w: Waker);
}
Starting tasks, waiting on keys, and the clock they sleep on: what an executor handles.
op spawn
fn spawn(f: held fn());
Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.
Starts f as a task of this executor, which decides when, and on which thread, it runs.
op stop
fn stop();
Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.
Asks the executor to stop: each of its tasks is cancelled at its next suspension, and a task spawned from then on before it starts, as the executor's doc says.
op yield_now
once fn yield_now();
Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.
Gives up the task's turn: it is ready again at once, behind the tasks ready now, so it is
a scheduling point (a choice under weir explore) that waits for nothing; a stopped
executor cancels the task there.
op preempt
once fn preempt();
Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.
A point where another task may run before this one goes on, as it may on any of an
executor's threads. An executor whose threads run tasks in parallel does nothing here:
they overtake the task anyway. std::sim's worlds, which run every task on one thread,
make it a scheduling point (systems.md §21.17): under Search a rare arm that runs every
other task ready now first. std::sync's waits begin with it; a wait built on waker
and park calls it where it begins to wait, after the check its wait depends on and
before it hands its key to the task that will wake it.
op waker
fn waker() -> Waker;
Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.
A fresh key to park one task under, once, and wake it by.
op park
once fn park(w: Waker);
Control kind once: the clause takes the continuation (cont k) and resumes or cancels it, so the perform is a suspension and an abort edge.
Suspends the task under w until w is woken, at once if it already was; a stopped
executor cancels the task there.
op wake
fn wake(w: Waker);
Control kind tail: the clause's result is the op's, and the performer goes on, never aborted or suspended.
Readies the task parked under w, or the park under w still to come.
handler Pool
pub handler Pool(
s: Slots<Unit, once>,
workers: usize,
stopped: Atomic<Bool>,
): shared strict cross for Tasks with Reactor, Sys, th: Threads
The work-stealing executor, for a program's tasks.
workers threads run ready tasks, each keeping its own queue and stealing the oldest task of
another when idle; a task costs its frame, not a thread. Install it as
with Pool(slots(), 4, atomic(false)) { … } (needs the root's Reactor, Sys and
Threads): slots() is its task table, workers must be at least 1 (with none a spawned
task never runs), and stopped must start unset. now reads the monotonic clock and sleep
parks the task on the reactor's timer, both in nanoseconds. The with ends once the body
and every task have ended.
Cancellation: once a task or the with body calls stop(), the pool cancels each task at its
next suspension. A task spawned from then on is cancelled before it starts (its move(…
on_abort …) entries run), a task that sleeps, yields or parks is cancelled from there (its
drop glue and on_abort blocks run), and a sleep in flight is asked to cancel (a timer that
has already fired wins, and its task is cancelled at its next sleep). A task already running,
or ready, runs to its next suspension. A task parked under a key no task wakes is cancelled
once every other task has ended.
Handles Tasks, with clauses: