<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://bil-lang.org/feed.xml" rel="self" type="application/atom+xml" /><link href="https://bil-lang.org/" rel="alternate" type="text/html" /><updated>2026-09-21T14:55:08+00:00</updated><id>https://bil-lang.org/feed.xml</id><title type="html">The Bil Language</title><subtitle>Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><author><name>{&quot;avatar&quot;=&gt;&quot;/assets/images/pipeline-mesh.png&quot;, &quot;bio&quot;=&gt;&quot;Source code:&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Bil toolchain&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/bil-lang/bil&quot;}, {&quot;label&quot;=&gt;&quot;Bil emulator&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/bil-lang/emulator&quot;}]}</name></author><entry><title type="html">Rethinking Classical Concurrency Patterns — the Bil Edition</title><link href="https://bil-lang.org/blog/rethinking-classical-concurrency-patterns-bil-edition/" rel="alternate" type="text/html" title="Rethinking Classical Concurrency Patterns — the Bil Edition" /><published>2026-09-21T00:00:00+00:00</published><updated>2026-09-21T00:00:00+00:00</updated><id>https://bil-lang.org/blog/rethinking-classical-concurrency-patterns-bil-edition</id><content type="html" xml:base="https://bil-lang.org/blog/rethinking-classical-concurrency-patterns-bil-edition/"><![CDATA[<p><a id="top"></a></p>

<p>This is a rewrite of Bryan C. Mills’ <a href="https://youtu.be/5zXAHh5tJqQ">GopherCon 2018 talk</a>, organized pattern-by-pattern rather than slide-by-slide. For each pattern the talk discusses in Go, this version shows how it looks in <a href="https://github.com/bil-lang/bil">Bil</a> — an extension to Go, built on the Go toolchain, that replaces goroutines-plus-buffered-channels with an occam-style Communicating Sequential Parallel model (<code class="language-plaintext highlighter-rouge">par</code>/<code class="language-plaintext highlighter-rouge">seq</code>/<code class="language-plaintext highlighter-rouge">alt</code>/<code class="language-plaintext highlighter-rouge">proc</code>, always-unbuffered channels, no <code class="language-plaintext highlighter-rouge">go</code> keyword) — and calls out the pros and cons of the Bil version against the original.</p>

<p>The talk’s own thesis is two rules, repeated at the start, middle, and end of the deck: <strong>“Start goroutines when you have concurrent work”</strong> and <strong>“Share by communicating.”</strong> Bil’s whole premise is taking those same two rules and making them compiler-enforced instead of idiomatic advice — there’s no <code class="language-plaintext highlighter-rouge">go</code> statement to reach for out of habit, and the static checker rejects code that shares memory across <code class="language-plaintext highlighter-rouge">par</code> branches instead of trusting the author to have read the blog post. That’s the thread running through every comparison below.</p>

<!--more-->

<hr />

<h2 id="summary">Summary</h2>

<table>
  <thead>
    <tr>
      <th>Pattern</th>
      <th>Bil verdict</th>
      <th>Why</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><a href="#pattern-1">Asynchronous callbacks</a></td>
      <td>Structurally impossible (a good thing)</td>
      <td><code class="language-plaintext highlighter-rouge">par</code> always joins before returning</td>
    </tr>
    <tr>
      <td><a href="#pattern-2">Futures</a></td>
      <td>Clean, near 1:1</td>
      <td><code class="language-plaintext highlighter-rouge">par</code> branches are already “start work, retrieve later”</td>
    </tr>
    <tr>
      <td><a href="#pattern-3">Producer–consumer queue</a></td>
      <td>Clean, near 1:1</td>
      <td>Unbuffered chan + <code class="language-plaintext highlighter-rouge">close</code>/<code class="language-plaintext highlighter-rouge">range</code> needs no translation</td>
    </tr>
    <tr>
      <td><a href="#pattern-4">Condition variables</a></td>
      <td>Replaced, mostly for the better</td>
      <td>3 of 4 named failure modes gone structurally; starvation needs opt-in <code class="language-plaintext highlighter-rouge">pri alt</code></td>
    </tr>
    <tr>
      <td><a href="#pattern-5">Semaphores / resource limits</a></td>
      <td>Ports, but heavier</td>
      <td>No buffered-channel-as-counter trick; needs an explicit process</td>
    </tr>
    <tr>
      <td><a href="#pattern-6">Broadcast / fan-out</a></td>
      <td>Excluded by design</td>
      <td><code class="language-plaintext highlighter-rouge">alt</code> is input-guard-only, so that no primitive resolves choice on both ends at once</td>
    </tr>
    <tr>
      <td><a href="#pattern-7">Worker pools (fixed N)</a></td>
      <td>Clean, <code class="language-plaintext highlighter-rouge">WaitGroup</code>-free</td>
      <td>Replicated <code class="language-plaintext highlighter-rouge">par</code> shares one channel legally; join is automatic</td>
    </tr>
    <tr>
      <td><a href="#pattern-7">Worker pools (dynamic <code class="language-plaintext highlighter-rouge">errgroup</code>-style cancel-all)</a></td>
      <td>Ports with real extra cost</td>
      <td>Needs one cancel channel per worker</td>
    </tr>
  </tbody>
</table>

<p><a href="#top">⇧ top</a></p>

<hr />

<p><a id="pattern-1"></a></p>

<h2 id="pattern-1-asynchronous-callbacks-the-rejected-pattern">Pattern 1: Asynchronous callbacks (the rejected pattern)</h2>

<p>Original (Go) — presented in the talk as the pattern to avoid:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Fetch immediately returns, then fetches the item and</span>
<span class="c">// invokes f in a goroutine when the item is available.</span>
<span class="c">// If the item does not exist,</span>
<span class="c">// Fetch invokes f on the zero Item.</span>
<span class="k">func</span> <span class="n">Fetch</span><span class="p">(</span><span class="n">name</span> <span class="kt">string</span><span class="p">,</span> <span class="n">f</span> <span class="k">func</span><span class="p">(</span><span class="n">Item</span><span class="p">))</span> <span class="p">{</span>
    <span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
        <span class="p">[</span><span class="err">…</span><span class="p">]</span>
        <span class="n">f</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
    <span class="p">}()</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>This is not how we write Go. (You likely know that already.)</p>
</blockquote>

<p><strong>The Bil take:</strong> this pattern isn’t just discouraged in Bil, it’s inexpressible. There is no <code class="language-plaintext highlighter-rouge">go</code> keyword — the only way to introduce concurrency is <code class="language-plaintext highlighter-rouge">par</code>, and a <code class="language-plaintext highlighter-rouge">par</code> block never returns control to its enclosing code until every branch has finished (fork <em>and</em> join, always). A <code class="language-plaintext highlighter-rouge">Fetch</code> written as a <code class="language-plaintext highlighter-rouge">proc</code> can’t launch background work and return before that work completes, because entering a <code class="language-plaintext highlighter-rouge">par</code> inside <code class="language-plaintext highlighter-rouge">Fetch</code> would just block <code class="language-plaintext highlighter-rouge">Fetch</code> itself until that <code class="language-plaintext highlighter-rouge">par</code> joins. So the “fire a goroutine, call back later, caller never synchronizes with it” shape doesn’t have a Bil translation — you’re routed straight to a Future or a queue, which is exactly what the rest of the talk argues for anyway.</p>

<p><strong>Pros vs. original:</strong> the bad pattern can’t be written by accident; there’s no footgun to avoid because there’s no gun. <strong>Cons:</strong> none, really — this is Bil structurally agreeing with the talk. It’s worth naming as its own point mostly because it shows Bil isn’t just “Go with stricter linting” — some things Go merely discourages, Bil’s grammar rules out.</p>

<p><a href="#top">⇧ top</a></p>

<hr />

<p><a id="pattern-2"></a></p>

<h2 id="pattern-2-futures">Pattern 2: Futures</h2>

<p>Original (Go):</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Fetch immediately returns a channel, then fetches</span>
<span class="c">// the requested item and sends it on the channel.</span>
<span class="c">// If the item does not exist,</span>
<span class="c">// Fetch closes the channel without sending.</span>
<span class="k">func</span> <span class="n">Fetch</span><span class="p">(</span><span class="n">name</span> <span class="kt">string</span><span class="p">)</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="n">Item</span> <span class="p">{</span>
    <span class="n">c</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="n">Item</span><span class="p">,</span> <span class="m">1</span><span class="p">)</span>
    <span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
        <span class="p">[</span><span class="err">…</span><span class="p">]</span>
        <span class="n">c</span> <span class="o">&lt;-</span> <span class="n">item</span>
    <span class="p">}()</span>
    <span class="k">return</span> <span class="n">c</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">a</span> <span class="o">:=</span> <span class="n">Fetch</span><span class="p">(</span><span class="s">"a"</span><span class="p">)</span>
<span class="n">b</span> <span class="o">:=</span> <span class="n">Fetch</span><span class="p">(</span><span class="s">"b"</span><span class="p">)</span>
<span class="n">consume</span><span class="p">(</span><span class="o">&lt;-</span><span class="n">a</span><span class="p">,</span> <span class="o">&lt;-</span><span class="n">b</span><span class="p">)</span>
</code></pre></div></div>

<blockquote>
  <p>The Go analogue to a Future is a single-element buffered channel. To use Futures for concurrency, the caller must set up concurrent work before retrieving results.</p>
</blockquote>

<p>Bil:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="n">proc</span> <span class="n">fetch</span><span class="p">(</span><span class="n">id</span> <span class="kt">int</span><span class="p">,</span> <span class="n">out</span> <span class="k">chan</span><span class="o">&lt;-</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">out</span> <span class="o">&lt;-</span> <span class="n">id</span> <span class="o">*</span> <span class="m">10</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">ac</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span>
    <span class="n">bc</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span>

    <span class="n">par</span> <span class="p">{</span>
        <span class="n">fetch</span><span class="p">(</span><span class="m">1</span><span class="p">,</span> <span class="n">ac</span><span class="p">)</span>
        <span class="n">fetch</span><span class="p">(</span><span class="m">2</span><span class="p">,</span> <span class="n">bc</span><span class="p">)</span>
        <span class="n">seq</span> <span class="p">{</span>
            <span class="k">var</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="kt">int</span>
            <span class="n">ac</span> <span class="o">-&gt;</span> <span class="n">a</span>
            <span class="n">bc</span> <span class="o">-&gt;</span> <span class="n">b</span>
            <span class="nb">println</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Pros vs. original:</strong> the buffered-channel-as-single-shot-future trick disappears entirely, because it’s no longer needed — <code class="language-plaintext highlighter-rouge">fetch(1, ac)</code> and <code class="language-plaintext highlighter-rouge">fetch(2, bc)</code> run as sibling <code class="language-plaintext highlighter-rouge">par</code> branches, so the “start both, then read both” call-site shape from the talk’s <code class="language-plaintext highlighter-rouge">Yes:</code> example is the <em>only</em> shape <code class="language-plaintext highlighter-rouge">par</code> offers you. The talk spends a whole slide (<code class="language-plaintext highlighter-rouge">Caller-side ambiguity</code>) warning that <code class="language-plaintext highlighter-rouge">a := &lt;-Fetch("a")</code> is a foot-gun because it silently serializes what looked concurrent; in Bil that mistake isn’t just discouraged, it’s a different, more verbose thing to write (you’d have to move the receive inside its own <code class="language-plaintext highlighter-rouge">seq</code> branch and manually break the parallelism), so it reads as deliberate rather than an easy slip. <strong>Cons:</strong> Go’s version is a one-liner return type (<code class="language-plaintext highlighter-rouge">&lt;-chan Item</code>) that composes with anything — store it, pass it around, fan it into a <code class="language-plaintext highlighter-rouge">select</code>. Bil’s channel is tied to a specific <code class="language-plaintext highlighter-rouge">proc</code> call inside a specific <code class="language-plaintext highlighter-rouge">par</code>; there’s no equivalent of stashing a “pending future” value in a struct field and resolving it later from unrelated code, since a channel’s reader and writer are fixed at the point the <code class="language-plaintext highlighter-rouge">par</code> is written, not assembled dynamically at runtime.</p>

<p><a href="#top">⇧ top</a></p>

<hr />

<p><a id="pattern-3"></a></p>

<h2 id="pattern-3-producerconsumer-queues">Pattern 3: Producer–consumer queues</h2>

<p>Original (Go):</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// Glob finds all items with names matching pattern</span>
<span class="c">// and sends them on the returned channel.</span>
<span class="c">// It closes the channel when all items have been sent.</span>
<span class="k">func</span> <span class="n">Glob</span><span class="p">(</span><span class="n">pattern</span> <span class="kt">string</span><span class="p">)</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="n">Item</span> <span class="p">{</span>
    <span class="n">c</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="n">Item</span><span class="p">)</span>
    <span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">defer</span> <span class="nb">close</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
        <span class="k">for</span> <span class="p">[</span><span class="err">…</span><span class="p">]</span> <span class="p">{</span>
            <span class="p">[</span><span class="err">…</span><span class="p">]</span>
            <span class="n">c</span> <span class="o">&lt;-</span> <span class="n">item</span>
        <span class="p">}</span>
    <span class="p">}()</span>
    <span class="k">return</span> <span class="n">c</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">item</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">Glob</span><span class="p">(</span><span class="s">"[ab]*"</span><span class="p">)</span> <span class="p">{</span>
    <span class="p">[</span><span class="err">…</span><span class="p">]</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>A channel fed by one goroutine and read by another acts as a queue. The consumer of a producer–consumer queue is usually a range-loop.</p>
</blockquote>

<p>Bil:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">const</span> <span class="n">n</span> <span class="o">=</span> <span class="m">5</span>

<span class="n">proc</span> <span class="n">glob</span><span class="p">(</span><span class="n">out</span> <span class="k">chan</span><span class="o">&lt;-</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">for</span> <span class="n">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">n</span> <span class="p">{</span>
        <span class="n">out</span> <span class="o">&lt;-</span> <span class="n">i</span> <span class="o">+</span> <span class="m">1</span>
    <span class="p">}</span>
    <span class="nb">close</span><span class="p">(</span><span class="n">out</span><span class="p">)</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">items</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span>
    <span class="n">par</span> <span class="p">{</span>
        <span class="n">glob</span><span class="p">(</span><span class="n">items</span><span class="p">)</span>
        <span class="n">seq</span> <span class="p">{</span>
            <span class="k">for</span> <span class="n">item</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">items</span> <span class="p">{</span>
                <span class="nb">println</span><span class="p">(</span><span class="n">item</span><span class="p">)</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Pros vs. original:</strong> this is the single closest match in the whole comparison — one producer proc, one consumer branch, <code class="language-plaintext highlighter-rouge">close</code>-to-signal-end, <code class="language-plaintext highlighter-rouge">range</code> to drain — because Go’s unbuffered-producer/single-consumer queue was already exactly the rendezvous shape Bil enforces everywhere. The <code class="language-plaintext highlighter-rouge">sync.Mutex</code>/shared-slice implementation the talk contrasts this with (the <code class="language-plaintext highlighter-rouge">Queue</code> type with <code class="language-plaintext highlighter-rouge">sync.Cond</code>, discussed next) simply has no Bil equivalent to reach for by mistake — there’s no shared mutable <code class="language-plaintext highlighter-rouge">items []Item</code> to protect, because the channel <em>is</em> the queue. <strong>Cons:</strong> Go’s <code class="language-plaintext highlighter-rouge">Glob</code> can be buffered (<code class="language-plaintext highlighter-rouge">make(chan Item, N)</code>) to decouple producer and consumer rates a little; Bil’s channel is always unbuffered, so a fast producer genuinely blocks on a slow consumer with zero slack. If you need slack, Bil pushes you toward Example 11 in the guide — a dedicated <code class="language-plaintext highlighter-rouge">buffer</code> process holding an internal <code class="language-plaintext highlighter-rouge">[]int</code> behind a guarded <code class="language-plaintext highlighter-rouge">alt</code>, which is more code than <code class="language-plaintext highlighter-rouge">make(chan Item, N)</code> but makes the buffering an explicit, visible process in the topology rather than a hidden channel property.</p>

<p><a href="#top">⇧ top</a></p>

<hr />

<p><a id="pattern-4"></a></p>

<h2 id="pattern-4-condition-variables-and-why-the-talk-moves-away-from-them">Pattern 4: Condition variables, and why the talk moves away from them</h2>

<p>The talk builds a <code class="language-plaintext highlighter-rouge">Queue</code> guarded by a <code class="language-plaintext highlighter-rouge">sync.Mutex</code> + <code class="language-plaintext highlighter-rouge">sync.Cond</code>:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="n">Queue</span> <span class="k">struct</span> <span class="p">{</span>
    <span class="n">mu</span> <span class="n">sync</span><span class="o">.</span><span class="n">Mutex</span>
    <span class="n">items</span> <span class="p">[]</span><span class="n">Item</span>
    <span class="n">itemAdded</span> <span class="n">sync</span><span class="o">.</span><span class="n">Cond</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">q</span> <span class="o">*</span><span class="n">Queue</span><span class="p">)</span> <span class="n">Get</span><span class="p">()</span> <span class="n">Item</span> <span class="p">{</span>
    <span class="n">q</span><span class="o">.</span><span class="n">mu</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
    <span class="k">defer</span> <span class="n">q</span><span class="o">.</span><span class="n">mu</span><span class="o">.</span><span class="n">Unlock</span><span class="p">()</span>
    <span class="k">for</span> <span class="nb">len</span><span class="p">(</span><span class="n">q</span><span class="o">.</span><span class="n">items</span><span class="p">)</span> <span class="o">==</span> <span class="m">0</span> <span class="p">{</span>
        <span class="n">q</span><span class="o">.</span><span class="n">itemAdded</span><span class="o">.</span><span class="n">Wait</span><span class="p">()</span>
    <span class="p">}</span>
    <span class="n">item</span> <span class="o">:=</span> <span class="n">q</span><span class="o">.</span><span class="n">items</span><span class="p">[</span><span class="m">0</span><span class="p">]</span>
    <span class="n">q</span><span class="o">.</span><span class="n">items</span> <span class="o">=</span> <span class="n">q</span><span class="o">.</span><span class="n">items</span><span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="p">]</span>
    <span class="k">return</span> <span class="n">item</span>
<span class="p">}</span>

<span class="k">func</span> <span class="p">(</span><span class="n">q</span> <span class="o">*</span><span class="n">Queue</span><span class="p">)</span> <span class="n">Put</span><span class="p">(</span><span class="n">item</span> <span class="n">Item</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">q</span><span class="o">.</span><span class="n">mu</span><span class="o">.</span><span class="n">Lock</span><span class="p">()</span>
    <span class="k">defer</span> <span class="n">q</span><span class="o">.</span><span class="n">mu</span><span class="o">.</span><span class="n">Unlock</span><span class="p">()</span>
    <span class="n">q</span><span class="o">.</span><span class="n">items</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">q</span><span class="o">.</span><span class="n">items</span><span class="p">,</span> <span class="n">item</span><span class="p">)</span>
    <span class="n">q</span><span class="o">.</span><span class="n">itemAdded</span><span class="o">.</span><span class="n">Signal</span><span class="p">()</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>Wait atomically unlocks the mutex and suspends the goroutine. Signal locks the mutex and wakes up the goroutine.</p>
</blockquote>

<p>…then names four specific failure modes that motivate abandoning this style: <strong>spurious wakeups</strong>, <strong>forgotten signals</strong>, <strong>starvation</strong>, and <strong>unresponsive cancellation</strong>. There is no Bil translation of <code class="language-plaintext highlighter-rouge">sync.Cond</code> at all — <code class="language-plaintext highlighter-rouge">sync.Mutex</code>/<code class="language-plaintext highlighter-rouge">sync.Cond</code> aren’t part of the model, so this pattern doesn’t get ported, it gets replaced by the buffer-process/<code class="language-plaintext highlighter-rouge">alt</code> idiom (Example 11 from the Bil guide):</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">const</span> <span class="n">capacity</span> <span class="o">=</span> <span class="m">2</span>
<span class="k">const</span> <span class="n">total</span> <span class="o">=</span> <span class="m">7</span>

<span class="n">proc</span> <span class="n">buffer</span><span class="p">(</span><span class="n">in</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="kt">int</span><span class="p">,</span> <span class="n">request</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="kt">int</span><span class="p">,</span> <span class="n">out</span> <span class="k">chan</span><span class="o">&lt;-</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">queue</span> <span class="p">[]</span><span class="kt">int</span>
    <span class="k">var</span> <span class="n">v</span> <span class="kt">int</span>
    <span class="k">var</span> <span class="n">emitted</span> <span class="o">=</span> <span class="m">0</span>

    <span class="k">for</span> <span class="n">emitted</span> <span class="o">&lt;</span> <span class="n">total</span> <span class="p">{</span>
        <span class="n">alt</span> <span class="p">{</span>
            <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span> <span class="o">&lt;</span> <span class="n">capacity</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">in</span> <span class="o">-&gt;</span> <span class="n">v</span> <span class="p">{</span>
                <span class="n">queue</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">queue</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span>
            <span class="p">}</span>
            <span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">queue</span><span class="p">)</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">request</span> <span class="o">-&gt;</span> <span class="n">_</span> <span class="p">{</span>
                <span class="n">out</span> <span class="o">&lt;-</span> <span class="n">queue</span><span class="p">[</span><span class="m">0</span><span class="p">]</span>
                <span class="n">queue</span> <span class="o">=</span> <span class="n">queue</span><span class="p">[</span><span class="m">1</span><span class="o">:</span><span class="p">]</span>
                <span class="n">emitted</span><span class="o">++</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>It’s worth checking the four failure modes against this one at a time, since they’re the whole reason the talk moves off condition variables:</p>

<ul>
  <li><strong>Spurious wakeups</strong> — Go’s <code class="language-plaintext highlighter-rouge">for len(q.items) == 0 { q.itemAdded.Wait() }</code> needs the <code class="language-plaintext highlighter-rouge">for</code> because <code class="language-plaintext highlighter-rouge">Wait()</code> can return without anything actually having changed. Bil’s <code class="language-plaintext highlighter-rouge">alt</code> guard <code class="language-plaintext highlighter-rouge">(len(queue) &lt; capacity) &amp;&amp; in -&gt; v</code> is re-evaluated fresh every time the surrounding <code class="language-plaintext highlighter-rouge">for</code> loop reaches it; there’s no separate “wake up, then go check” step to desynchronize from the actual condition, because the guard <em>is</em> the condition, checked at the moment of the (still synchronous) rendezvous.</li>
  <li><strong>Forgotten signals</strong> — in Go, a <code class="language-plaintext highlighter-rouge">Put</code> that calls <code class="language-plaintext highlighter-rouge">Signal()</code> before any goroutine is inside <code class="language-plaintext highlighter-rouge">Wait()</code> loses that wakeup, which is exactly why <code class="language-plaintext highlighter-rouge">Wait()</code> has to sit in a re-checking loop rather than a one-shot <code class="language-plaintext highlighter-rouge">if</code>. Bil has no separate signal event to lose in the first place: an early <code class="language-plaintext highlighter-rouge">in &lt;- v</code> from a producer just blocks until <code class="language-plaintext highlighter-rouge">buffer</code>’s <code class="language-plaintext highlighter-rouge">alt</code> loop comes back around to that guard — nothing is “sent into the void” the way a <code class="language-plaintext highlighter-rouge">Signal()</code> can be.</li>
  <li><strong>Starvation</strong> — this one Bil does <em>not</em> solve for free. A plain <code class="language-plaintext highlighter-rouge">alt</code> desugars to Go’s own <code class="language-plaintext highlighter-rouge">select</code>, and <code class="language-plaintext highlighter-rouge">select</code> is documented as picking pseudo-randomly among ready cases — the guide’s own Example 9 confirms this empirically (plain <code class="language-plaintext highlighter-rouge">alt</code> between a <code class="language-plaintext highlighter-rouge">high</code> and <code class="language-plaintext highlighter-rouge">low</code> sender came back <code class="language-plaintext highlighter-rouge">low</code>×5 then <code class="language-plaintext highlighter-rouge">high</code>×5 in one run, the opposite of any priority). What Bil adds is <code class="language-plaintext highlighter-rouge">pri alt</code>, a documented, compiler-supported way to force priority ordering instead of leaving it to chance — the same example shows <code class="language-plaintext highlighter-rouge">pri alt</code> giving <code class="language-plaintext highlighter-rouge">high</code>×5 then <code class="language-plaintext highlighter-rouge">low</code>×5, 5/5 runs. Go’s <code class="language-plaintext highlighter-rouge">sync.Cond</code> has no equivalent knob at all.</li>
  <li><strong>Unresponsive cancellation</strong> — the talk’s fix in Go is threading a <code class="language-plaintext highlighter-rouge">context.Context</code> through a <code class="language-plaintext highlighter-rouge">select</code>. Bil’s fix is the same shape: a cancel channel is just another <code class="language-plaintext highlighter-rouge">alt</code> guard, no different from any other. Validated directly — adding a <code class="language-plaintext highlighter-rouge">cancel &lt;-chan int</code> branch to a semaphore-style <code class="language-plaintext highlighter-rouge">alt</code> and sending on it from a sibling <code class="language-plaintext highlighter-rouge">seq</code> branch after a short sleep terminates the pool process cleanly and exits, exactly like the <code class="language-plaintext highlighter-rouge">ctx.Done()</code> case in Go’s <code class="language-plaintext highlighter-rouge">select</code>.</li>
</ul>

<p><strong>Pros vs. original:</strong> three of the four named failure modes (spurious wakeups, forgotten signals, unresponsive cancellation) go away structurally rather than by discipline — there’s no mutex/condvar pair to get subtly wrong, because the rendezvous itself carries the synchronization. <strong>Cons:</strong> starvation is not solved by default, only solvable — you have to notice you need <code class="language-plaintext highlighter-rouge">pri alt</code> and reach for it; a <code class="language-plaintext highlighter-rouge">sync.Cond</code>-based Go program has the same exposure and the same lack of a built-in fix, so this is closer to a wash than a Bil win, just a differently-shaped one (an explicit opt-in primitive vs. no primitive at all). More generally: the buffer-process idiom is more lines than the mutex/condvar version for the same capacity-2 queue — Bil trades line count for removing an entire class of memory-sharing bug.</p>

<p><a href="#top">⇧ top</a></p>

<hr />

<p><a id="pattern-5"></a></p>

<h2 id="pattern-5-resource-limits-as-resources-semaphores">Pattern 5: Resource limits as resources (semaphores)</h2>

<p>The talk’s Go progression goes from a <code class="language-plaintext highlighter-rouge">sync.Cond</code>-guarded pool, to “resource limits are resources too,” to a buffered channel used as a semaphore, with cancellation added via <code class="language-plaintext highlighter-rouge">select</code>:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">p</span> <span class="o">*</span><span class="n">Pool</span><span class="p">)</span> <span class="n">Acquire</span><span class="p">(</span><span class="n">ctx</span> <span class="n">context</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span> <span class="p">(</span><span class="n">net</span><span class="o">.</span><span class="n">Conn</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">select</span> <span class="p">{</span>
    <span class="k">case</span> <span class="n">conn</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="n">p</span><span class="o">.</span><span class="n">idle</span><span class="o">:</span>
        <span class="k">return</span> <span class="n">conn</span><span class="p">,</span> <span class="no">nil</span>
    <span class="k">case</span> <span class="n">p</span><span class="o">.</span><span class="n">sem</span> <span class="o">&lt;-</span> <span class="n">token</span><span class="p">{}</span><span class="o">:</span>
        <span class="n">conn</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">dial</span><span class="p">()</span>
        <span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
            <span class="o">&lt;-</span><span class="n">p</span><span class="o">.</span><span class="n">sem</span>
        <span class="p">}</span>
        <span class="k">return</span> <span class="n">conn</span><span class="p">,</span> <span class="n">err</span>
    <span class="k">case</span> <span class="o">&lt;-</span><span class="n">ctx</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span><span class="o">:</span>
        <span class="k">return</span> <span class="no">nil</span><span class="p">,</span> <span class="n">ctx</span><span class="o">.</span><span class="n">Err</span><span class="p">()</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>When we block on communicating, others can also communicate with us: for example, to cancel the call.</p>
</blockquote>

<p>Bil has its own dedicated worked example for exactly this — Example 16 in the guide, a P/V semaphore process built from <code class="language-plaintext highlighter-rouge">alt</code> guards over per-client channels:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">proc</span> <span class="n">semaphore</span><span class="p">(</span><span class="n">chans</span> <span class="p">[]</span><span class="k">chan</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">count</span> <span class="o">:=</span> <span class="n">maxCount</span>
    <span class="n">held</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">bool</span><span class="p">,</span> <span class="n">nClients</span><span class="p">)</span>
    <span class="k">for</span> <span class="k">range</span> <span class="n">nClients</span> <span class="o">*</span> <span class="n">cycles</span> <span class="o">*</span> <span class="m">2</span> <span class="p">{</span>
        <span class="n">alt</span> <span class="n">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">nClients</span> <span class="p">{</span>
            <span class="p">(</span><span class="n">held</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">||</span> <span class="n">count</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">chans</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">-&gt;</span> <span class="n">_</span> <span class="p">{</span>
                <span class="k">if</span> <span class="n">held</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">{</span>
                    <span class="n">held</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="no">false</span>
                    <span class="n">count</span><span class="o">++</span>
                <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
                    <span class="n">held</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="no">true</span>
                    <span class="n">count</span><span class="o">--</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>and adding cancellation to a similar pool follows the same pattern as any other <code class="language-plaintext highlighter-rouge">alt</code>, validated directly:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">proc</span> <span class="n">pool</span><span class="p">(</span><span class="n">acquire</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="kt">int</span><span class="p">,</span> <span class="n">release</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="kt">int</span><span class="p">,</span> <span class="n">cancel</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">held</span> <span class="o">:=</span> <span class="m">0</span>
    <span class="k">for</span> <span class="p">{</span>
        <span class="n">alt</span> <span class="p">{</span>
            <span class="p">(</span><span class="n">held</span> <span class="o">&lt;</span> <span class="n">limit</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">acquire</span> <span class="o">-&gt;</span> <span class="n">_</span> <span class="p">{</span>
                <span class="n">held</span><span class="o">++</span>
            <span class="p">}</span>
            <span class="p">(</span><span class="n">held</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">release</span> <span class="o">-&gt;</span> <span class="n">_</span> <span class="p">{</span>
                <span class="n">held</span><span class="o">--</span>
            <span class="p">}</span>
            <span class="n">cancel</span> <span class="o">-&gt;</span> <span class="n">_</span> <span class="p">{</span>
                <span class="k">return</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Pros vs. original:</strong> Go’s version quietly relies on <code class="language-plaintext highlighter-rouge">p.sem &lt;- token{}</code> succeeding <em>because</em> the channel is buffered — the buffer capacity <em>is</em> the semaphore count, which is elegant but means the semaphore’s whole state lives inside a channel’s internal buffer, invisible to anyone reading the type. Bil’s semaphore is a real, visible process (Example 16’s <code class="language-plaintext highlighter-rouge">semaphore</code> proc) with an explicit <code class="language-plaintext highlighter-rouge">count</code>/<code class="language-plaintext highlighter-rouge">held</code> — you can read the state machine directly instead of inferring it from a buffer capacity. The cancellation guard costs nothing extra beyond one more <code class="language-plaintext highlighter-rouge">alt</code> arm, same as Go’s <code class="language-plaintext highlighter-rouge">select</code>. <strong>Cons:</strong> Bil’s version needs a full dedicated process plus one channel <em>per client</em> (<code class="language-plaintext highlighter-rouge">chans []chan bool</code>), because there’s no buffered channel to lean on as a free counting primitive — Go gets a counting semaphore in three lines (<code class="language-plaintext highlighter-rouge">make(chan token, limit)</code>); Bil needs a proc, an array of channels, and explicit bookkeeping. The convenience of “buffer capacity as a data structure” is exactly the thing Bil’s “always unbuffered” rule gives up.</p>

<p><a href="#top">⇧ top</a></p>

<hr />

<p><a id="pattern-6"></a></p>

<h2 id="pattern-6-broadcast--fan-out-completion--a-deliberate-exclusion-not-a-gap">Pattern 6: Broadcast / fan-out completion — a deliberate exclusion, not a gap</h2>

<p>The talk’s <code class="language-plaintext highlighter-rouge">Idler</code> uses <code class="language-plaintext highlighter-rouge">Broadcast()</code> (and, in the channel-based version, <code class="language-plaintext highlighter-rouge">close(idle)</code>) so that an arbitrary, unknown-in-advance number of callers can all wake up on one event:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">func</span> <span class="p">(</span><span class="n">i</span> <span class="o">*</span><span class="n">Idler</span><span class="p">)</span> <span class="n">SetBusy</span><span class="p">(</span><span class="n">b</span> <span class="kt">bool</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">idle</span> <span class="o">:=</span> <span class="o">&lt;-</span><span class="n">i</span><span class="o">.</span><span class="n">next</span>
    <span class="k">if</span> <span class="n">b</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">idle</span> <span class="o">==</span> <span class="no">nil</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">idle</span> <span class="o">=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="k">struct</span><span class="p">{})</span>
    <span class="p">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">!</span><span class="n">b</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">idle</span> <span class="o">!=</span> <span class="no">nil</span><span class="p">)</span> <span class="p">{</span>
        <span class="nb">close</span><span class="p">(</span><span class="n">idle</span><span class="p">)</span> <span class="c">// Idle now.</span>
        <span class="n">idle</span> <span class="o">=</span> <span class="no">nil</span>
    <span class="p">}</span>
    <span class="n">i</span><span class="o">.</span><span class="n">next</span> <span class="o">&lt;-</span> <span class="n">idle</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote>
  <p>Broadcast usually communicates events that affect all waiters.</p>
</blockquote>

<p>This is the one pattern that does <strong>not</strong> port as a direct translation. Bil enforces that a channel is read in at most one <code class="language-plaintext highlighter-rouge">par</code> branch. In Bil, <code class="language-plaintext highlighter-rouge">alt</code> only supports input guards — there’s no way to guard on ‘is anyone ready to receive from me,’ so a buffer can’t offer output the same way it offers input.</p>

<p><em>Why?</em></p>

<p>An input guard’s readiness is a local, one-hop fact: “is this guard ready?” only ever depends on whether one specific process is currently trying to send — no negotiation needed, just a check. An output guard’s readiness isn’t local in the same way: if a process could <code class="language-plaintext highlighter-rouge">alt</code> over several possible <em>sends</em>, whether any one of them is ready depends on whether the receiver on the other end is itself, right now, willing to commit to that specific pairing — and if that receiver is also running an <code class="language-plaintext highlighter-rouge">alt</code> choosing among several potential senders, neither side can resolve its own readiness without first knowing the other side’s still-undetermined choice. That circular dependency is the same shape as the classic distributed matching/commitment problem, and resolving it for real needs an actual protocol (propose, acknowledge, commit, or similar) — at which point different, individually reasonable protocol choices (who proposes first, how retries or ties are broken, whether backoff is randomized) can produce different livelock or deadlock behavior for the exact same program. The deadlock outcome becomes a property of the matching implementation rather than of the program’s CSP semantics — precisely “implementation specific deadlock conditions.” Go’s own <code class="language-plaintext highlighter-rouge">select</code> <em>does</em> support output guards (<code class="language-plaintext highlighter-rouge">case ch &lt;- v:</code>), but only gets away with it because it isn’t actually distributed: every channel named in one <code class="language-plaintext highlighter-rouge">select</code> is resolved against a single runtime’s one global scheduler lock, in one address space. That’s exactly the shared-memory assumption Bil’s whole placement story — processes with no shared memory, safely movable onto physically separate processors — is built to not need, so a primitive that needs a global arbiter to stay safe doesn’t fit.</p>

<p><strong>The solution:</strong> give every waiter its own channel and have the event source hold one channel per waiter, so the “broadcast” becomes <code class="language-plaintext highlighter-rouge">N</code> ordinary point-to-point sends — legal Bil, but only if the set of waiters is a fixed, known-in-advance <code class="language-plaintext highlighter-rouge">N</code> (so both sides can be expressed with <code class="language-plaintext highlighter-rouge">par i := range N</code>, folding back into the replicated case that <em>is</em> allowed) rather than a genuinely dynamic registry of callers arriving at arbitrary times, which the talk’s <code class="language-plaintext highlighter-rouge">Idler.AwaitIdle</code> explicitly supports and Bil has no equivalent primitive for. One sender, <code class="language-plaintext highlighter-rouge">N</code> receivers:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">const</span> <span class="n">n</span> <span class="o">=</span> <span class="m">3</span>

<span class="n">proc</span> <span class="n">waiter</span><span class="p">(</span><span class="n">id</span> <span class="kt">int</span><span class="p">,</span> <span class="n">done</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">done</span> <span class="o">-&gt;</span> <span class="n">_</span>
    <span class="nb">println</span><span class="p">(</span><span class="s">"waiter"</span><span class="p">,</span> <span class="n">id</span><span class="p">,</span> <span class="s">"done"</span><span class="p">)</span>
<span class="p">}</span>

<span class="n">proc</span> <span class="n">broadcaster</span><span class="p">(</span><span class="n">chans</span> <span class="p">[]</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">par</span> <span class="n">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">n</span> <span class="p">{</span>
        <span class="n">chans</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">&lt;-</span> <span class="m">0</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">chans</span> <span class="o">:=</span> <span class="n">makeChans</span><span class="p">[</span><span class="kt">int</span><span class="p">](</span><span class="n">n</span><span class="p">)</span>
    <span class="n">par</span> <span class="p">{</span>
        <span class="n">broadcaster</span><span class="p">(</span><span class="n">chans</span><span class="p">)</span>
        <span class="n">par</span> <span class="n">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">n</span> <span class="p">{</span>
            <span class="n">waiter</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">chans</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">broadcaster</code> is a single <code class="language-plaintext highlighter-rouge">proc</code> — one logical sender, matching the shape of the original <code class="language-plaintext highlighter-rouge">Idler</code> — but its body is itself a <em>replicated</em> <code class="language-plaintext highlighter-rouge">par i := range n { chans[i] &lt;- 0 }</code>, so the <code class="language-plaintext highlighter-rouge">n</code> individual sends fire concurrently rather than being forced into program order by a sequential loop; each is still exactly one writer (<code class="language-plaintext highlighter-rouge">broadcaster</code>’s replica <code class="language-plaintext highlighter-rouge">i</code>) paired with exactly one reader (<code class="language-plaintext highlighter-rouge">waiter</code>’s replica <code class="language-plaintext highlighter-rouge">i</code>) on <code class="language-plaintext highlighter-rouge">chans[i]</code>, so it satisfies the same one-reader-one-writer rule as everything else in this document, just <code class="language-plaintext highlighter-rouge">n</code> times over instead of once.</p>

<p><strong>Pros vs. original:</strong> this is the price of a guarantee, not an oversight — restricting <code class="language-plaintext highlighter-rouge">alt</code> to input guards is what keeps every synchronization event in a Bil program one-sided (at most one party ever making a nondeterministic choice), and that’s exactly what makes the static checks, and the deadlock-freedom-by-construction and safe-placement-onto-disjoint-processors story, tractable in the first place. Admitting output guards to close this gap would mean admitting the distributed matching problem above right back in. <strong>Cons:</strong> the mechanical cost is real regardless of why it exists — Go’s <code class="language-plaintext highlighter-rouge">close(ch)</code> gives every current and future receiver of that channel a simultaneous, race-free wakeup for free, and that “future receiver” part (a goroutine that hasn’t even called <code class="language-plaintext highlighter-rouge">AwaitIdle</code> yet when <code class="language-plaintext highlighter-rouge">SetBusy(false)</code> runs) is precisely what Bil’s static, fixed-at-compile-time topology can’t express.</p>

<p><a href="#top">⇧ top</a></p>

<hr />

<p><a id="pattern-7"></a></p>

<h2 id="pattern-7-worker-pools">Pattern 7: Worker pools</h2>

<p>The talk runs through four Go variants: a shared-queue pool, the same pool with <code class="language-plaintext highlighter-rouge">sync.WaitGroup</code> cleanup, a semaphore-channel “inverted” pool, and (in the backup slides) <code class="language-plaintext highlighter-rouge">errgroup.WithContext</code> with <code class="language-plaintext highlighter-rouge">SetLimit</code>. Take the classic shared-queue version:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">work</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="n">Task</span><span class="p">)</span>
<span class="k">for</span> <span class="n">n</span> <span class="o">:=</span> <span class="n">limit</span><span class="p">;</span> <span class="n">n</span> <span class="o">&gt;</span> <span class="m">0</span><span class="p">;</span> <span class="n">n</span><span class="o">--</span> <span class="p">{</span>
    <span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">for</span> <span class="n">task</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">work</span> <span class="p">{</span>
            <span class="n">perform</span><span class="p">(</span><span class="n">task</span><span class="p">)</span>
        <span class="p">}</span>
    <span class="p">}()</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">task</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">hugeSlice</span> <span class="p">{</span>
    <span class="n">work</span> <span class="o">&lt;-</span> <span class="n">task</span>
<span class="p">}</span>
<span class="nb">close</span><span class="p">(</span><span class="n">work</span><span class="p">)</span>
<span class="n">wg</span><span class="o">.</span><span class="n">Wait</span><span class="p">()</span>
</code></pre></div></div>

<p>Bil:</p>

<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>

<span class="k">const</span> <span class="n">limit</span> <span class="o">=</span> <span class="m">3</span>
<span class="k">const</span> <span class="n">total</span> <span class="o">=</span> <span class="m">9</span>
<span class="k">const</span> <span class="n">done</span> <span class="o">=</span> <span class="o">-</span><span class="m">1</span>

<span class="n">proc</span> <span class="n">worker</span><span class="p">(</span><span class="n">id</span> <span class="kt">int</span><span class="p">,</span> <span class="n">work</span> <span class="o">&lt;-</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
    <span class="k">var</span> <span class="n">task</span> <span class="kt">int</span>
    <span class="k">for</span> <span class="p">{</span>
        <span class="n">work</span> <span class="o">-&gt;</span> <span class="n">task</span>
        <span class="k">if</span> <span class="n">task</span> <span class="o">==</span> <span class="n">done</span> <span class="p">{</span>
            <span class="k">return</span>
        <span class="p">}</span>
        <span class="nb">println</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">task</span><span class="p">)</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
    <span class="n">work</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span>
    <span class="n">par</span> <span class="p">{</span>
        <span class="n">par</span> <span class="n">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">limit</span> <span class="p">{</span>
            <span class="n">worker</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">work</span><span class="p">)</span>
        <span class="p">}</span>
        <span class="n">seq</span> <span class="p">{</span>
            <span class="k">for</span> <span class="n">t</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">total</span> <span class="p">{</span>
                <span class="n">work</span> <span class="o">&lt;-</span> <span class="n">t</span>
            <span class="p">}</span>
            <span class="k">for</span> <span class="k">range</span> <span class="n">limit</span> <span class="p">{</span>
                <span class="n">work</span> <span class="o">&lt;-</span> <span class="n">done</span>
            <span class="p">}</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p><strong>Pros vs. original:</strong> the <code class="language-plaintext highlighter-rouge">sync.WaitGroup</code> disappears entirely — <code class="language-plaintext highlighter-rouge">par</code>’s own join already waits for every one of the <code class="language-plaintext highlighter-rouge">limit</code> replicated <code class="language-plaintext highlighter-rouge">worker</code> instances to return, which is exactly what <code class="language-plaintext highlighter-rouge">wg.Wait()</code> was doing by hand. <code class="language-plaintext highlighter-rouge">close(work)</code> still works as the shutdown signal in the sense that Go code does, but note the actual example above uses an explicit sentinel (<code class="language-plaintext highlighter-rouge">done = -1</code>) instead, matching the guide’s own idiom (Example 15’s poison pill) — worth trying <code class="language-plaintext highlighter-rouge">close</code>+<code class="language-plaintext highlighter-rouge">range</code> here too, since Pattern 3 confirmed both are legal Bil; either shuts the pool down cleanly. Either way, the pool’s whole lifecycle — start, dispatch, shut down, join — is shorter in Bil than the talk’s “cleaning up” slide, precisely because <code class="language-plaintext highlighter-rouge">par</code> folds the <code class="language-plaintext highlighter-rouge">WaitGroup</code> bookkeeping in for free. <strong>Cons:</strong> <code class="language-plaintext highlighter-rouge">errgroup.WithContext(ctx)</code> with <code class="language-plaintext highlighter-rouge">SetLimit</code> and “cancel every worker on the first error” doesn’t have a clean Bil equivalent, for the same reason as Pattern 6 — propagating one cancellation to <code class="language-plaintext highlighter-rouge">limit</code> already-running workers is a fixed-N fan-out (doable: give each worker its own cancel channel, loop sending to each), but there’s no single primitive that does it in one call the way closing <code class="language-plaintext highlighter-rouge">ctx</code>’s internal channel does in Go. And the dynamic, escape-time-varies-per-item load balancing the guide’s own Mandelbrot example (<code class="language-plaintext highlighter-rouge">farmer</code>/<code class="language-plaintext highlighter-rouge">worker</code> with per-worker <code class="language-plaintext highlighter-rouge">assign</code>/<code class="language-plaintext highlighter-rouge">result</code> channels and an <code class="language-plaintext highlighter-rouge">alt w := range nWorkers</code> collecting whichever result is ready first) needs an explicit dispatcher process and one channel pair <em>per worker</em> — more moving parts than <code class="language-plaintext highlighter-rouge">errgroup</code>’s implicit shared queue, in exchange for a topology you can read straight off the source rather than infer from a library’s internals.</p>

<p><a href="#top">⇧ top</a></p>

<hr />

<h2 id="closing-thoughts">Closing thoughts</h2>

<p>The overall shape: patterns that were already “one sender, one receiver, synchronous” in Go (futures, simple queues, semaphores, fixed-size pools) translate to Bil with little or no ceremony, because that was already the shape Bil’s rendezvous model wants. Patterns that lean on Go’s channels being able to have an <em>arbitrary, dynamic</em> number of readers (<code class="language-plaintext highlighter-rouge">close</code>-to-broadcast, <code class="language-plaintext highlighter-rouge">errgroup</code>’s implicit shared cancellation) are exactly where Bil’s static, compiler-checked topology pushes back — not a bug, but a direct consequence of the same design that makes Bil’s processes safely placeable on physically separate processors with no shared memory (per <code class="language-plaintext highlighter-rouge">docs/guide.md</code>’s stated purpose), which is a guarantee Go’s dynamic channel plumbing can’t offer at all.</p>

<p><a href="#top">⇧ top</a></p>]]></content><author><name>{&quot;avatar&quot;=&gt;&quot;/assets/images/pipeline-mesh.png&quot;, &quot;bio&quot;=&gt;&quot;Source code:&quot;, &quot;links&quot;=&gt;[{&quot;label&quot;=&gt;&quot;Bil toolchain&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/bil-lang/bil&quot;}, {&quot;label&quot;=&gt;&quot;Bil emulator&quot;, &quot;icon&quot;=&gt;&quot;fab fa-fw fa-github&quot;, &quot;url&quot;=&gt;&quot;https://github.com/bil-lang/emulator&quot;}]}</name></author><category term="Blog" /><category term="concurrency" /><category term="CSP" /><category term="occam" /><category term="Go" /><summary type="html"><![CDATA[This is a rewrite of Bryan C. Mills’ GopherCon 2018 talk, organized pattern-by-pattern rather than slide-by-slide. For each pattern the talk discusses in Go, this version shows how it looks in Bil — an extension to Go, built on the Go toolchain, that replaces goroutines-plus-buffered-channels with an occam-style Communicating Sequential Parallel model (par/seq/alt/proc, always-unbuffered channels, no go keyword) — and calls out the pros and cons of the Bil version against the original. The talk’s own thesis is two rules, repeated at the start, middle, and end of the deck: “Start goroutines when you have concurrent work” and “Share by communicating.” Bil’s whole premise is taking those same two rules and making them compiler-enforced instead of idiomatic advice — there’s no go statement to reach for out of habit, and the static checker rejects code that shares memory across par branches instead of trusting the author to have read the blog post. That’s the thread running through every comparison below.]]></summary></entry></feed>