Actor Models (C++ Concurrency & Lock-Free)
Learning Goal: Designing and implementing a custom actor-model concurrency framework from scratch in C++ to understand lock-free message queues, thread-pool scheduling, and supervisor hierarchies.
Prerequisites
- Proficient C++: Comfort with pointers, templates, RAII, move semantics, and standard library containers (C++11 or newer).
- Basic Computer Systems Knowledge: Understanding of memory hierarchies, caches, registers, and basic OS concepts like context switching and processes.
Estimated Study Time
45 Hours (including video lectures, theoretical analysis, and hands-on implementation phases).
Module 1: C++ Multi-Threading Foundations
This module introduces operating system thread management, C++ thread control flows, and basic synchronization primitives. You will learn to construct, manage, and synchronize execution flows while avoiding data races and resource leaks, utilizing modern C++20 patterns like standard cooperative cancellation.
Recommended Videos
- Why this video is valuable: This video provides a gentle, practical introduction to basic C++ multi-threading. It introduces how
std::threadmatches hardware threads and executes callables asynchronously, acting as the starting point for your concurrency journey. - Knowledge Checkpoint:
- Spin up a simple asynchronous worker using
std::thread. - Understand why calling
.join()or.detach()is mandatory before astd::threadobject goes out of scope. - Pass parameters safely by value and reference (
std::ref) into a thread function.
- Spin up a simple asynchronous worker using
- Why this video is valuable: Anthony Williams (author of C++ Concurrency in Action) covers modern C++20 synchronization mechanics. This is crucial for modernizing our actor framework's core runtime engine with
std::jthread(cooperative cancellation), latches (std::latch), and barriers (std::barrier). - Knowledge Checkpoint:
- Implement cooperative thread cancellation using
std::stop_tokenandstd::jthread. - Distinguish when to use a single-use
std::latchversus a reusablestd::barrierfor thread synchronization. - Explain how C++20 synchronization features help avoid complex custom condition-variable logic.
- Implement cooperative thread cancellation using
- Why this video is valuable: This video isolates mutual exclusion primitives (
std::mutex) and demonstrates how unguarded memory access leads to undefined data races. It establishes the "lock-based" baseline we will optimize away later in the course. - Knowledge Checkpoint:
- Identify a data race in shared code and fix it using
std::mutexandstd::lock_guard. - Explain how RAII lock managers (
std::unique_lock,std::lock_guard) guarantee safe lock release during exceptions. - Formulate a scenario that causes a deadlock and list methods to prevent it (e.g., ordering or
std::lock).
- Identify a data race in shared code and fix it using
Module 2: Thread Pools and Task Scheduling
To build an actor framework, you must avoid the high overhead of spawning OS threads dynamically. This module covers implementing a reusable, highly efficient worker thread pool and a task scheduler using modern C++ features.
Recommended Videos
- Why this video is valuable: A complete walk-through of a standard, lock-based thread pool in C++. It demonstrates how to manage worker lifetimes and dispatch tasks via a synchronized shared queue. This serves as the initial architectural backbone for executing our actor workloads.
- Knowledge Checkpoint:
- Construct a pool of workers that block on a
std::condition_variablewhen idle. - Safely push generic callables (using type-erased
std::functionor custom packages) into a thread-safe task queue. - Implement a clean pool termination sequence that handles both running tasks and pending tasks without causing deadlocks.
- Construct a pool of workers that block on a
- Why this video is valuable: Preshing explores real-world task-scheduling architectures designed for high performance. This helps transition your mindset from a simple centralized lock-based queue to distributed, fine-grained task execution architectures.
- Knowledge Checkpoint:
- Explain why a centralized, lock-heavy task queue can become a system bottleneck.
- Understand the concept of task-stealing or decentralized scheduling across multiple worker queues.
- Define how to decompose large, monolithic programs into independent, asynchronous task units.
- Why this video is valuable: This video focuses on
std::future,std::promise, andstd::packaged_task. These asynchronous primitives let tasks return values safely across thread boundaries, which is essential for implementing request-response patterns (likeaskorrequest) in our actor system. - Knowledge Checkpoint:
- Bind a task using
std::packaged_taskand dispatch it to your thread pool. - Retrieve an asynchronous value safely using
std::future::getand handle exceptions thrown in background threads. - Contrast
std::async's default execution policies with dispatching tasks directly to a custom thread pool.
- Bind a task using
Module 3: Atomics and Lock-Free Queue Design
Locking primitives degrade throughput under heavy thread contention. This module covers lock-free programming using C++ atomic operations and memory ordering, culminating in designing high-performance Single-Producer Single-Consumer (SPSC) and Multi-Producer Multi-Consumer (MPMC) lock-free queues.
Recommended Videos
- Why this video is valuable: Fedor Pikus delivers an exceptional, deep-dive lecture on C++ atomics, hardware-level caches, and memory barriers. This presentation equips you with the mental model needed to understand atomic read-modify-write loops and memory ordering.
- Knowledge Checkpoint:
- Explain how hardware cache lines, cache coherence (MESI), and memory barriers affect multi-threaded throughput.
- Understand why
std::atomic<T>::is_lock_free()is critical and check compiler assumptions. - Build a lock-free spinlock using
std::atomic_flagand evaluate its performance againststd::mutex.
- Why this video is valuable: Timur Doumler walks through lock-free structures, demonstrating how to design a high-throughput Single-Producer Single-Consumer (SPSC) queue using a circular ring buffer. This highlights the crucial distinction between SPSC and MPMC architectures.
- Knowledge Checkpoint:
- Diagram a lock-free circular SPSC queue utilizing atomic head and tail indexes.
- Apply
std::memory_order_acquireandstd::memory_order_releaseto maximize queue performance without relying on sequential consistency. - Explain why a lock-free SPSC queue avoids internal race conditions without requiring CAS (Compare-And-Swap) loops.
- Why this video is valuable: This video unpacks the complexities of implementing a Multi-Producer Multi-Consumer (MPMC) lock-free queue using a ring buffer with generation-based tracking. This design matches what a multi-sender, multi-threaded actor system needs for its mailboxes.
- Knowledge Checkpoint:
- Implement a Compare-And-Swap (CAS) loop using
std::atomic::compare_exchange_weakto safely update shared queue indexes. - Explain generation-based encoding or versioned index techniques to solve the ABA problem in lock-free ring buffers.
- Describe the differences in performance, safety, and complexity between SPSC, MPMC, and MPSC (Multi-Producer Single-Consumer) configurations.
- Implement a Compare-And-Swap (CAS) loop using
Module 4: Designing the Actor Model Architecture
This module covers the core concepts of the Actor Model, showing how isolated state, asynchronous message passing, and share-nothing memory layouts provide high scalability compared to traditional thread-and-lock Object-Oriented patterns.
Recommended Videos
- Why this video is valuable: A classic discussion featuring Carl Hewitt, the pioneer of the Actor Model. It provides the deep theoretical foundation of the paradigm, focusing on three essential elements: internal processing, local state storage, and asynchronous message communications.
- Knowledge Checkpoint:
- Define Hewitt's mathematical axioms of an Actor: what actions can an actor perform upon receiving a message?
- Explain how isolated, unshareable state eliminates the need for shared synchronization locks in application logic.
- Explain why message passing in the Actor Model is inherently asynchronous and decoupled in space and time.
- Why this video is valuable: Kevlin Henney compares passive lock-based objects with active, message-driven actors. This distinction helps you shift your design approach away from traditional, call-and-block OO designs.
- Knowledge Checkpoint:
- Differentiate between passive synchronization (e.g., monitor objects with locks) and active concurrent entities (actors).
- Explain the concept "one damn message after another" and how it guarantees sequential execution inside a single actor's scope.
- Formulate a thread-safe message dispatch system that avoids deadlocks by converting method calls into asynchronous messages.
- Why this video is valuable: This video demonstrates Erlang's real-world implementation of the Actor Model, showing how isolated processes, message mailboxes, and share-nothing state form a robust concurrency framework.
- Knowledge Checkpoint:
- Understand how mailboxes function as asynchronous processing buffers for incoming actor messages.
- Describe how the Erlang virtual machine schedules thousands of lightweight, independent processes.
- Explain why sharing mutable memory across actor boundaries is forbidden in standard actor-model patterns.
Module 5: Building the Actor Framework in C++
Here, you will construct your custom C++ actor framework. This involves building the core abstract Actor class, attaching a lock-free queue as its mailbox, and writing the underlying Dispatcher to bind your actors to your thread pool.
Recommended Videos
- Why this video is valuable: This video shows a concrete implementation of an Actor base class in C++. It demonstrates how to use runtime polymorphism, base virtual handlers, and customized internal messaging mechanisms.
- Knowledge Checkpoint:
- Implement an abstract base
Actorclass featuring a virtualon_receivemessage processing handler. - Integrate a lock-free queue (acting as the actor's mailbox) inside the
Actorinstance. - Define how an actor schedules itself to run on the thread pool when it transitions from "idle" to "has pending messages."
- Implement an abstract base
- Why this video is valuable: Daniela Engert explains how to build an active, non-blocking execution context where agents run concurrently on a shared dispatcher. This shows how to decouple task generation from execution threads in C++.
- Knowledge Checkpoint:
- Construct a global execution dispatcher that coordinates thread execution with actor mailboxes.
- Write a scheduler loop where worker threads pull active actors, process a batch of their mailbox messages, and release them back to the dispatcher.
- Explain how to handle actor lifetimes safely when an actor is destroyed while message processing is still active.
Architectural Blueprint for Module 5
Because hands-on tutorials for custom C++ actor systems are rare, use the following blueprint to guide your implementation:
+----------------------------------------------+
| Dispatcher |
| (Coordinates Thread Pool & Active Actors) |
+----------------------+-----------------------+
|
Registers / Pushes Active Actors
|
v
+----------------------------------------------+
| Thread Pool |
| [Thread 1] [Thread 2] [Thread ... ] |
+----------------------+-----------------------+
|
Pulls an Actor and processes its
mailbox sequentially
|
v
+---------------------------------------+
| Actor Base |
| - Mailbox (Lock-free MPMC Queue) |
| - State: Idle / Scheduled / Running |
| - on_receive(Message) [Pure Virtual] |
+---------------------------------------+
- The Message Type: Create a type-erased message class (e.g., using
std::anyor a custom variant union) so actors can receive different payload types safely. - The Mailbox: Embed an atomic MPMC queue into each actor. When actor A sends a message to actor B, it pushes the payload into B's queue.
- The Scheduling State: Use an atomic state flag inside each actor (e.g.,
enum State { Idle, Scheduled, Running }). If actor B's state isIdlewhen A pushes a message, change B's state toScheduledand enqueue B's raw pointer into theDispatcher's global execution queue. - The Execution Loop: The thread pool's workers fetch scheduled actors from the
Dispatcher. The thread sets the actor's state toRunning, pops a predefined batch of messages from its mailbox, processes them sequentially by calling the virtualon_receive, and then reverts the state toIdle(unless more messages arrived, in which case it schedules itself again).
Self-Study Search Query: "C++ actor model dispatcher implementation std::any" or "C++ thread pool processing scheduled actors lock-free"
Module 6: Supervisor Hierarchies & Fault Tolerance
This final module focuses on fault-tolerant systems using actor supervisor hierarchies. You will learn how to design parent-child relationships where parents monitor, restart, or safely tear down child processes.
Recommended Videos
- Why this video is valuable: Joe Armstrong (co-creator of Erlang) presents his foundational philosophy: "Let it crash." He explains how isolating crashes inside individual actors and handling failures in supervisors creates resilient concurrent systems.
- Knowledge Checkpoint:
- Explain why traditional exception handling (try-catch) often fails in highly asynchronous, concurrent systems.
- Describe the "Let it crash" philosophy and why isolating failures to individual actors protects the overall system.
- Understand why actors must have distinct, isolated lifecycles managed by designated supervisors.
- Why this video is valuable: Explores Erlang's OTP (Open Telecom Platform) framework, detailing supervision trees, monitoring links, and restart strategies. This provides a blueprint for structuring supervisor hierarchies in our framework.
- Knowledge Checkpoint:
- Diagram a hierarchical supervision tree consisting of nested root supervisors, sub-supervisors, and leaf-level worker actors.
- Contrast Erlang’s restart policies: One-For-One (restart only the failed child) and One-For-All (restart all siblings if one fails).
- Explain how supervisors distinguish between expected child termination and unexpected runtime errors.
- Why this video is valuable: This lecture explores OTP supervision internals, focusing on why transient failures are resolved by restarts and how to handle persistent faults without getting stuck in infinite restart loops.
- Knowledge Checkpoint:
- Explain how a supervisor uses error thresholds (e.g., maximum restarts within a specific time window) to handle persistent faults.
- Describe how failures propagate up a supervision hierarchy if a local supervisor cannot recover a child.
- Translate these supervision strategies into your custom C++ framework.
Architectural Blueprint for Module 6
To implement supervisor hierarchies in C++, follow this structured guide to fill the gap of technical videos:
+-------------------------------------------+
| Supervisor Actor |
| - Monitored Children List |
| - Restart Strategy: One-For-One / All |
| - on_child_failure(ChildPtr, Reason) |
+---------------------+---------------------+
|
Supervises / Spawns / Restarts
|
v
+-------------------------------------------+
| Worker Actor |
| - Supervisor Reference |
| - Run context (might throw exception) |
+-------------------------------------------+
- Parent-Child Links: When an actor spawns another actor, register the child's raw pointer or handle (
ActorRef) inside the parent. The parent acts as its supervisor. - Monitoring & Exception Isolation: Wrap the virtual processing run loop of the worker actor in a
try-catchblock inside your thread pool's task execution wrapper. - Failure Propagation: If a worker actor throws an uncaught exception during message processing:
- Catch it at the thread pool execution wrapper level.
- Do not crash the worker thread. Instead, wrap the failure event in a system control message (e.g.,
ChildFailedMessage) containing the child's identifier and the exception details. - Asynchronously route this
ChildFailedMessagestraight into the parent's mailbox.
- Handling Failures in the Supervisor: Inside the parent's
on_receive, intercept this control message:- For One-For-One: Re-instantiate the failed child actor class, restore its initial state, and re-bind its mailbox.
- For One-For-All: Send shutdown signals to all other registered sibling actors under this supervisor, wait for confirmation, and restart them all.
- Track restarts using a counter and a timer. If an actor crashes too frequently (e.g., more than 5 times in 10 seconds), escalate the failure by forwarding the crash report to the supervisor's own parent.
Self-Study Search Query: "C++ supervisor pattern implementation" or "handling child actor crashes in C++ thread pool"
Course Map
Below is the recommended sequence of modules. Solid arrows denote direct dependencies.
Key People Index
- Anthony Williams: Author of the standard reference book C++ Concurrency in Action. He is a key contributor to C++ standardization for concurrency primitives and is the maintainer of the
just::threadC++11 concurrency library. - Fedor Pikus: Chief Particle Physicist at Mentor Graphics and a regular speaker at CppCon. He is widely recognized for his clear explanations of low-level hardware interactions, lock-free patterns, and high-performance design.
- Timur Doumler: Developer advocate at JetBrains, active SG14 (Game Dev & Low Latency) C++ committee member, and specialist in audio and low-latency programming. He is a primary source for real-time lock-free practices.
- Carl Hewitt: Computer scientist and Professor Emeritus at MIT who formulated the Actor Model in 1973 as a formal model of concurrent computation.
- Joe Armstrong (1950–2019): Co-creator of the Erlang programming language and principal designer of the Open Telecom Platform (OTP) framework. His work defined modern fault-tolerant concurrent systems architecture.
Final Self-Assessment
Verify your understanding and custom framework implementation against these key requirements:
- Thread Resource Safety: My thread pool uses C++20
std::jthreadand cooperative cancellation tokens to safely spin down and clean up resources without leaving orphaned threads or leaks. - No Raw Mutex Locks in Mailboxes: My actors' message mailboxes are implemented using a lock-free Single-Producer Single-Consumer (SPSC) or Multi-Producer Multi-Consumer (MPMC) queue that does not rely on mutex locks.
- Lock-free Queue Atomicity: My lock-free queues use explicit memory orderings (
std::memory_order_release/std::memory_order_acquire) and CAS loops, verified to be data-race-free under sanitizers. - State Isolation: No two actor instances share raw pointers to mutable states. All information exchanges occur exclusively via immutable values passed as typed or type-erased messages.
- Dynamic Task Scheduling: The dispatcher dynamically allocates CPU execution slices on the thread pool to active actors with pending messages, returning them to an idle state once their mailboxes are clear.
- Exception Containment: The thread pool's task executor intercepts unexpected exceptions in worker actors. It isolates the crash, preventing the worker thread itself from terminating.
- Parent-Child Supervision Tree: I have implemented parent-child links that let parent actors monitor child lifecycles and receive notification messages if a child fails.
- Supervisor Restart Policy: My supervisor actors support at least one configurable recovery strategy (such as One-For-One or One-For-All) to restart failed child actors and restore them to a clean state.
- Escalation & Infinite Loops: My supervisors track child restart frequencies, safely escalating failures up the tree if a child hits its maximum restart threshold within a given time frame.







![Lock-free programming with modern C++ - Timur Doumler [ACCU 2017]](https://i.ytimg.com/vi_webp/qdrp6k4rcP4/maxresdefault.webp)








