Communicating Sequential Processes (CSP) Model in Clojure

Learn the core ideas of CSP, how core.async approximates them in Clojure, and where explicit channel communication beats shared mutable state.

CSP (Communicating Sequential Processes): A concurrency model in which independent processes coordinate by exchanging values through channels rather than sharing mutable state directly.

CSP matters in Clojure because it gives a mental model for core.async that is more useful than “async but with channels.” The key idea is simple:

  • each process owns its local work
  • communication is explicit
  • synchronization happens through message handoff

That is different from building concurrency around many threads reaching into the same mutable structure.

What Clojure Borrows From CSP

core.async is not a pure academic CSP implementation, but it captures the practical lessons well:

  • channels as communication boundaries
  • independent processes around those channels
  • explicit waiting and selection

This makes the communication graph visible in the code.

Why Explicit Communication Helps

Channels make handoff points concrete:

1(def requests (async/chan 32))
2(def responses (async/chan 32))

Now the design has visible questions:

  • who owns writes to requests?
  • who consumes them?
  • what happens when the channel fills?
  • when is it closed?

Those questions are harder to ignore than they are with shared mutable state.

CSP Is Mostly About Ownership

The most valuable CSP lesson is not syntax. It is ownership. Each process should be responsible for:

  • one stage
  • one stream
  • one small responsibility

If many processes are still coordinating through hidden shared state, the code has borrowed channel syntax without adopting the model.

Where CSP Fits Well in Clojure

It fits best when:

  • work moves through stages
  • producers and consumers should stay loosely coupled
  • pacing and buffering matter
  • the system is event or message oriented

It fits poorly when:

  • one mutable value is the real coordination center
  • the problem is a simple one-shot background task
  • channels are only being added for novelty

Channel Protocols Still Need Design

A useful CSP-style channel answers:

  • what kind of values move on it
  • who is responsible for closing it
  • what end-of-stream means
  • whether the stream is lossless or best-effort

Without those rules, the program may use channels while still leaving the communication contract implicit.

Closure Is Part of the Protocol

In CSP-style designs, channel closure is not a cleanup detail. It is part of the meaning of the stream. A consumer needs to know whether closure means normal completion, upstream failure, no more work for this partition, or system shutdown.

That is why good channel design documents:

  • who is allowed to close the channel
  • whether multiple producers exist
  • what downstream code should do when closure arrives
  • whether a separate error path exists for abnormal termination

CSP vs STM

This is a useful contrast:

  • STM coordinates shared in-memory invariants
  • CSP coordinates independent processes by communication

If several values must change together atomically, refs and transactions are the right story. If the main problem is staged communication between independent actors, channels are often the clearer story.

Common Failure Mode

The most common misuse of CSP style in Clojure is keeping the shared mutable design and merely wrapping it with channels. If every process still depends on global mutable coordination, the model has not really changed.

Practical Rule

Use the CSP model to reason about core.async systems in terms of ownership and message flow. If the communication edges are explicit and the state remains local where possible, the design is usually much easier to scale and debug.

Revised on Thursday, April 23, 2026