1 min read

Communicating Sequential Processes (CSP)

Communicating Sequential Processes (CSP) is a formal language for describing patterns of interaction in concurrent systems. It is a model for concurrency where independent processes communicate by sharing data through channels.

1. Core Concepts

  • Processes: Independent units of execution (like threads, but often lighter). They do not share memory.
  • Channels: The pipes through which processes communicate. A process writes to a channel, and another process reads from it.
  • Synchronization: Communication is the synchronization point. If a process tries to write to a channel, it blocks until another process is ready to read (and vice-versa), unless the channel is buffered.

2. Go (Golang) Implementation

Go's concurrency model is heavily based on CSP.

  • Goroutines: Lightweight processes managed by the Go runtime.
  • Channels: Typed conduits for sending and receiving values.
package main

import "fmt"

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        fmt.Println("worker", id, "started  job", j)
        results <- j * 2
        fmt.Println("worker", id, "finished job", j)
    }
}

func main() {
    jobs := make(chan int, 100)
    results := make(chan int, 100)

    // Start 3 workers (Processes)
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }

    // Send 5 jobs (Communication via Channel)
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)

    // Collect results
    for a := 1; a <= 5; a++ {
        <-results
    }
}

3. CSP vs. Actor Model

Both models deal with concurrency via message passing, but they differ in focus.

Feature CSP (Go, Clojure) Actor Model (Akka, Erlang)
Focus The Channel (transport) The Actor (entity)
Coupling Processes are anonymous; they only know the channel. Actors know the identity (address) of other actors.
Communication Synchronous (usually blocking) Asynchronous (Fire and Forget)
Mailbox No (unless buffered channel) Yes (each actor has a mailbox)

4. Benefits

  • No Shared Memory: Avoids the complexity of locks, mutexes, and race conditions associated with shared memory concurrency. "Do not communicate by sharing memory; instead, share memory by communicating."
  • Reasoning: It is often easier to reason about data flow through channels than complex state mutations in shared objects.
  • Composability: Channels can be passed around as first-class citizens.

5. The Select Statement (Go)

The select statement is a powerful control structure in Go that lets a goroutine wait on multiple communication operations. It acts like a switch statement, but for channels.

  • Non-Blocking: It blocks until one of its cases can run, then it executes that case.
  • Random Selection: If multiple cases are ready, one is chosen at random.
  • Timeouts: Can be used to implement timeouts for channel operations.
select {
case msg1 := <-c1:
    fmt.Println("received", msg1)
case msg2 := <-c2:
    fmt.Println("received", msg2)
case <-time.After(1 * time.Second):
    fmt.Println("timeout")
}

programming/actor-model programming/asynchronous-programming programming/distributed-systems