Skip to content
core.async — Channels and Go Blocks
step 1/7

Reading — step 1 of 7

Learn

~3 min readcore.async, Java Interop, Performance

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 pmap or 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-let for 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 thread for 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…