Clojure core.async — Channels and Go Blocks (CSP Concurrency) | Episode 29

0views
C
CelesteAI
Description
Clojure has one more concurrency story, and it's the one that scales furthest: **CSP-style** — Communicating Sequential Processes. Instead of shared state with locks (atoms, refs, agents), you build producers and consumers as independent lightweight processes (`go` blocks) that pass messages through channels. In this episode we pull in `core.async`, create a buffered channel with three slots, write a producer that puts five messages on it, and a consumer `go-loop` that reads until the channel closes. Five messages flow through cleanly, and the consumer exits when it sees `nil` from a closed channel. Student code: https://github.com/GoCelesteAI/clojure-for-beginners/tree/main/episode29 Every keystroke is shown on screen with generous pauses so you can follow along at your own pace. What You'll Learn: - Adding `core.async` via `deps.edn` - `(chan)` and `(chan n)` — unbuffered vs buffered FIFO queues - `(go body)` — spin up a lightweight process that **parks** (not blocks) on channel ops - put-bang / take-bang — parking put/take; **only inside go blocks** - put-bang-bang / take-bang-bang — blocking put/take; **only outside go blocks** (REPL, main thread) - `(close! ch)` — end-of-stream signal; consumers see `nil` - `go-loop` as idiomatic consumer pattern with `when-let` - Why channels scale further than agents — many producers, many consumers, backpressure Timestamps: 0:00 - Intro 0:15 - Preview: messages, not locks 0:51 - deps.edn with core.async 1:16 - The code — producer + consumer via a channel 1:38 - Start the REPL 1:52 - Require core.async 2:06 - (chan) — unbuffered 2:13 - The channel object 2:19 - (go (put-bang ch :hello)) 2:26 - (take-bang-bang ch) — :hello 2:35 - (chan 3) — buffered 2:41 - Put three and close 2:47 - Take them out — 1, 2, 3 3:07 - Take again — nil (closed) 3:18 - Control-D 3:27 - clj -M:run — the full pipeline 3:44 - Recap 4:28 - What's next: Episode 30 Key Takeaways: 1. A channel is a FIFO queue. Buffered channels let the producer run ahead up to N items; unbuffered channels synchronize each put with its take. 2. `go` blocks are not threads — they're lightweight processes that park on channel ops and resume when the op is ready. One real thread can host hundreds of go blocks. 3. **Inside go: use the single-bang ops (take-bang, put-bang)** — they park. **Outside go: use the double-bang ops (take-bang-bang, put-bang-bang)** — they block a real thread. Getting this wrong pins a thread and defeats the whole model. 4. Closed channels are the exit signal: a take on a closed, empty channel returns `nil`. `go-loop` + `when-let` + `recur` is the idiomatic consumer shape. 5. Channels decouple producers from consumers. Swap a single producer for 10, or a single consumer for 10 — the channel absorbs the rate difference. Phase 5's last primitive. Next up: the concurrency decision guide — `atom` vs `ref` vs `agent` vs `future` vs core.async, compared. Taught by CelesteAI. Like and subscribe for more Clojure tutorials!
Back to tutorials

Duration

Added to Codegiz

April 20, 2026

📖 Read the articleOpen in YouTube