Three earlier pages each showed one face of the same thing. Channel streams put a producer behind a bounded ring and later joined a second one with into. A room is a stream leaned on that fan-in for its relay. The worker pool went the other way — one stream, N pulling workers. This page says out loud what those pages implied: underneath all of them sits one machine, the bounded multi-producer multi-consumer ring, and the surface’s spellings are not features stacked on features — each one states a fact about that ring. Then it runs both directions at once.

The example is examples/MpmcPipeline in the tutorial repository: three producers, one ring, four workers.

Getting the file

make -C examples/MpmcPipeline run
make -C examples/MpmcPipeline run MICA_EXTRA_FLAGS="--tasking multicore,4"

Both commands — one carrier or four — print the same two lines:

three producers, one ring, four workers
  delivered 24 of 24
  sum 48108 - every value in exactly one worker

Each producer’s values carry its tag in the thousands, so all 24 values are unique and that sum can only be 48108 if every value was delivered exactly once — the self-checking output, in the concurrent world, where it earns its keep for real.

The example, walked

The ring, said three ways

s := schedule Produce(1, PerProducer) buffer 4;
schedule Produce(2, PerProducer) into s;
schedule Produce(3, PerProducer) into s;

concurrent
    schedule Worker(0, s);
    schedule Worker(1, s);
    schedule Worker(2, s);
    schedule Worker(3, s);
end;

Eleven lines, three spellings, each a static fact the compiler holds:

  • schedule … buffer 4 binds s to a ring and starts its first producer. Ring-ness is a fact of the bind, not a runtime discovery.
  • into s joins another producer to the ring that is already open — fan-in is a second attach, never a second mechanism.
  • s as a stream of task parameter is what admits four workers pulling one variable — the fan-out the ring was always able to serve, stated where the compiler can check it.

Every value is popped by exactly one worker. Completeness is the ring’s promise; placement belongs to the scheduler, which is why the program prints joined totals and no per-worker breakdown — the honest report is the one no interleaving can change.

Partition, don’t share

task Worker(w : int64, jobs : stream of int64);
...
    for job in jobs do
    begin
        c := c + 1;
        t := t + job;
    end;

    synchronized count[w] := c;
    synchronized total[w] := t;

The hot loop takes no lock and needs none: each worker tallies into its own locals, and each parks its result in its own slot — count[w], nobody else’s. synchronized appears exactly twice in the whole program, where a finished worker makes its slot visible to the owner that joins the scope. That is the design rule this page most wants you to take away: partition state so no lock is needed on the hot path, and let synchronized mark the one place sharing genuinely happens. The compiler enforces the marking — the second experiment shows it refusing the unmarked touch.

Backpressure, and the end

The buffer 4 is the whole flow-control policy. A producer that runs ahead parks on the full ring; a worker that outruns production parks on the empty one; nothing spins, nothing drops, and no knob exists to mistune. And the stream ends when the last of the three producers ends — an into-joined ring is not done until everyone who feeds it is.

Merge, or select

into and select both face several producers; they answer different questions. Merge with into when the values are one workload and arrival order is yours to surrender — you get one stream, exactly-once delivery, and totals like this page’s. Reach for select — taught at channel streams — when the sources must stay distinguishable: who spoke first matters, or a timeout arm needs to catch nobody speaking at all. One ring merging, or several rings raced; the design choice is which question your consumer is really asking.

Try it

1. Four carriers. Run the second command from the top of the page five times if you like — the same two lines, every time. The totals are not usually-right; they are the ring’s promise.

2. Unmark the sharing. Remove synchronized from the count[w] := c; line and rebuild:

analyzer error 5337: a task may touch the program's global variable 'count' only in a 'synchronized'-marked statement: while tasks may be running, a global is shared state on every core, so the access must be spelled 'synchronized' to make that sharing visible — outside the shared region no mark is needed

The data-race law from the data-races page, holding in the middle of a real pipeline: sharing is spelled where it happens, or the program does not build.

3. Lose the ring. Change the bind line to s := Produce(1, PerProducer); — a pull chain instead of a ring — and rebuild:

analyzer error 5393: the 'into' target must be a stream variable already bound by 'schedule', but 's' is not: the clause joins a second producer to an existing ring — bind the stream first with '<variable> := schedule <generator>(...)'

Fan-in needs the ring, and the refusal says so with the fix spelled out — ring-ness is a static fact, which is exactly why all of this can be checked before anything runs.

What this page proved

Exactly-once delivery is printed, not promised: 24 of 24, sum 48108, one carrier or four, on both architectures. The sharing discipline is compiled, not reviewed: the unmarked touch and the ring-less fan-in are refusals with their fixes in the message. And the machine underneath — the runtime’s ring — is exercised under ThreadSanitizer in the compiler’s own gates on every merge, which is what lets a tutorial print “every value in exactly one worker” as a fact rather than a hope.

Next

The streams shelf, in one breath: generators pull, channel streams push and select, the worker pool fans out, the chat server fans in across a network, and this page ran both directions through the one ring underneath them all. Every example lives in the tutorial repository.