Skip to content

The Actor Model & Concurrency

Axon eschews traditional lock-based multi-threading in favor of an M:N Actor model. This ensures thread-safety at compile time without the cognitive overhead of complex borrow checking or manual lock management.

In languages like C++ or Java, multiple threads often read and write to the same memory locations. To prevent data races, developers must use mutexes, semaphores, or locks. This approach frequently leads to:

  • Deadlocks: Two threads waiting on each other indefinitely.
  • Race conditions: Bugs that only appear under specific timing conditions.
  • Performance bottlenecks: Threads spending most of their time waiting for locks to release.

Rust solved this with the borrow checker, ensuring memory safety at compile time. However, the borrow checker can be famously difficult to satisfy, leading to steep learning curves and slower iteration times.

Instead of sharing state, Axon isolates it.

Every concurrent process in Axon is an Actor. An Actor encapsulates its own state and executes sequentially. Actors communicate exclusively by passing immutable messages to each other asynchronously.

Because state is never shared between Actors, data races are impossible.

Under the hood, Axon uses an M:N scheduler. This means that millions of lightweight Axon Actors (M) are multiplexed onto a small number of native OS threads (N) — typically one thread per logical CPU core.

When an Actor blocks on I/O (like waiting for a network request or reading a file), the Axon runtime automatically suspends it and schedules another Actor to run on that OS thread. This ensures that the CPU is never idle and that high-concurrency applications scale effortlessly.

Here is what spawning an Actor and sending a message looks like in Axon:

(defactor worker
(on-message (msg)
(println "Worker received:" msg)))
(fn main ()
;; Spawn the actor
(let pid (spawn worker))
;; Send it a message asynchronously
(send pid "Hello, concurrent world!"))

Because Axon’s syntax is homoiconic S-expressions, the compiler can trivially verify that messages passed between actors are immutable, guaranteeing thread-safety at zero runtime cost.