Reading — step 1 of 7
Learn
core.async is Clojure's library for CSP-style concurrency — channels, processes, and go blocks. Inspired by Go and Hoare's Communicating Sequential Processes. Standard for Clojure async code.
Note: core.async is a separate library, not in core. In a project: [org.clojure/core.async "1.6.681"]. The Judge0 environment may not include it.
Channels — typed mailboxes
(require '[clojure.core.async :as a])
(def ch (a/chan)) ;; unbuffered channel
(def buffered (a/chan 10)) ;; buffered, holds up to 10
Channels are first-class values. Producers >! (put), consumers <! (take). Both block until the other side is ready (for unbuffered).
go blocks — lightweight processes
(let [ch (a/chan)]
(a/go (a/>! ch "hello")) ;; producer
(a/go (println (a/<! ch)))) ;; consumer
go blocks are LIGHTWEIGHT processes (not threads). Thousands can coexist. >! and <! work only inside go. From outside, use >!! and <!! (the blocking variants).
Pipeline pattern
(defn -main []
(let [in (a/chan)
out (a/chan)]
;; Producer:
(a/go (doseq [x (range 10)]
(a/>! in x))
(a/close! in))
;; Worker:
(a/go (loop []
(when-let [v (a/<! in)]
(a/>! out (* v v))
(recur)))
(a/close! out))
;; Consumer:
(a/<!! (a/go (loop []
(when-let [v (a/<! out)]
(println v)
(recur)))))))
Three go blocks: producer feeds in, worker reads in and writes squared values to out, consumer prints them. CSP composition.
alts! — receive from multiple
(let [ch1 (a/chan)
ch2 (a/chan)]
(a/go
(a/>! ch1 "first"))
(a/go
(Thread/sleep 100)
(a/>! ch2 "second"))
(let [[val ch] (a/alts!! [ch1 ch2])]
(println "got" val "from" (if (= ch ch1) "ch1" "ch2"))))
alts! (or alts!!) takes a vector of channels and waits for the first one to provide a value. Like Go's select.
Buffered vs unbuffered
- Unbuffered — strict synchronization; producer waits for consumer (rendezvous)
- Buffered —
(chan 10)— producer can write up to 10 before blocking - Dropping —
(chan (a/dropping-buffer 10))— drops new on full - Sliding —
(chan (a/sliding-buffer 10))— drops old on full
Different backpressure strategies for different needs.
When to use core.async
Yes:
- Coordinating async work
- Producer/consumer patterns
- Multiplexing inputs (alts!)
- Building reactive pipelines
No:
- Simple parallel work — use
pmapor futures - Single-shot async — just use a future / promise
- IO-heavy work — channels add overhead; sometimes plain Java threads are cleaner
Comparison with futures and promises
;; future — runs body in another thread, returns deref-able
(def f (future (Thread/sleep 100) 42))
@f ;; 42 (blocks until ready)
;; promise — write-once, deref-many
(def p (promise))
(future (deliver p 42))
@p
Futures and promises are simpler. core.async is for complex coordination patterns.
Common mistakes
- Mixing
>!and>!!—>!only inside go blocks;>!!outside. The same applies to<!/<!!. - Forgetting to close channels — consumers using
when-letfor graceful termination need the channel to close. Producers should(a/close! ch)when done. - Blocking I/O inside go blocks — go uses a small thread pool; blocking calls starve other gos. Use
threadfor blocking work; pipe results via channel. - Channel leaks — orphaned channels with pending writes pin memory. Close when done.
- CPU work in go — go is for I/O-style coordination. Heavy compute belongs on regular threads.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…