Channel streams told the push half of the stream story: a producer scheduled behind a bounded ring, fan-in as a second attach. This page is the other direction — one stream into N consumers — and the whole trick is that it is not a trick. The runtime’s ring was always multi-consumer; what 7.0.0 adds is the language’s permission to say so, and the permission is a static fact, not a runtime discovery.
The example is
examples/WorkerPool.
Build and run it:
make -C examples/WorkerPool run
make -C examples/WorkerPool run MICA_EXTRA_FLAGS="--tasking multicore,4"Ring-ness is a fact of the bind
Two spellings bind a stream variable, and they have never meant the same thing:
s := Produce(40); { a pull chain: one frozen activation }
s := schedule Produce(40) buffer 8; { a task-fed ring: MPMC machinery }The first freezes a generator the puller resumes; the second starts the body as a producing task behind a bounded ring. Since 7.0.0 the compiler carries that difference as a property of the variable itself — a stream variable is a ring or a chain for its whole life, decided by its binds — and every fan-out rule reads that one fact.
The pool
task Worker(w : int64, s : stream of int64);
var
job, count, total : int64;
begin
count := 0;
total := 0;
for job in s do
begin
count := count + 1;
total := total + job * job;
end;
synchronized doneCount[w] := count;
synchronized doneWeight[w] := total;
end;A stream of parameter on a task is the fan-out shape, and it is
admitted exactly when the schedule’s argument is statically a ring:
s := schedule Produce(Jobs) buffer 8;
concurrent
schedule Worker(0, s);
schedule Worker(1, s);
schedule Worker(2, s);
end;Three workers pull one variable. The ring’s pop lands each value in the popping task’s own pull word, so every job is delivered to exactly one worker — that is the machine’s guarantee, proven under ThreadSanitizer, and now the surface’s promise too.
Completeness, not placement
Which worker takes which job is scheduling’s business, and the example is honest about it: nothing per-worker is printed. What is printed admits no loss and no double delivery:
WorkerPool
jobs done: 40
weight: 2214022140 is the machine-checked sum of the squares 1²..40². The same two
lines appear at --tasking single, multicore,2 and multicore,4, on
both architectures — the fan-out twin of the promise the
chat server makes for fan-in.
Backpressure needs no knob: a full ring parks the producer, an empty one parks the pullers, and the stream’s end is the producer ending.
What the compiler refused
| The program tried to | The compiler said |
|---|---|
| hand a pull chain to a task | 5448: the scheduled task 'Worker' pulls the stream variable 's' from another task, which only a task-fed ring supports: bind it as 's := schedule <generator>(...) buffer <capacity>' … |
bind s as a ring on one line and a chain on another | 5447: the stream variable 's' is bound as a task-fed ring at one site and as a pull chain at another: ring-ness is one fact for the variable's whole life … |
| rebind the ring while workers consume it | 5449: … the bind that created its ring must be its only bind: a later bind would replace the ring the running consumers still pull … |
schedule … into a chain | 5393: the 'into' target must be a stream variable already bound by 'schedule' … (this was a runtime trap before 7.0.0; ring-ness made it a compile answer) |
What this does not do
- A chain still cannot fan out, ever. One frozen activation cannot serve two pulling tasks; 5411 keeps guarding the pull chain unchanged.
- No work stealing, no shards. The pool is the ring’s own fairness: whoever is ready pops next. If you need affinity, give each worker its own ring and route jobs yourself — the shape stays three lines.
- Owner pulls are allowed, and they compete. The owner joining its own workers is one more consumer, not a race — but the jobs it takes, the workers do not.
Try it
Add a fourth worker and re-run at multicore,4: the two printed lines do
not change. Then delete the word schedule from the bind and meet 5448
in its full form — the fix is spelled inside it.
Next
The pool’s jobs here are integers. The network line is where the same shape meets sockets: accept feeds a ring, workers serve connections.