Theron - C++ concurrency library

The Theron threadpool

The conceptual model of actors is that they each have a unique thread inside them. The internal thread is woken to execute the message handlers of the actor whenever a message arrives.

Indeed, this is the most obvious way to implement an actor-based system: Every time an actor is constructed, start a thread and assign the handle to the actor. But naturally that would quickly lead to a large number of threads.

So in Theron, actor message handlers are executed by a fixed pool of dedicated worker threads that share the work between them. This is called an M:N architecture, meaning that the number of concurrent entities (M) is different from the actual number of threads (N).

Actors that have received messages are queued on a single (thread-safe) work queue, and the worker threads are woken by the arrival of new work. A woken thread retrieves an actor from the queue and executes any message handlers that the actor has registered for the message type it has received.

The multi-processing of message handlers by worker threads is not pre-emptive, so once a worker thread has started executing an actor's message handlers, it will continue executing them until they are completed, rather than switching to a different actor halfway through. This implies that badly behaved actors can starve other actors in the system. In order to ensure that actors with messages to be processed are not starved indefinitely (sometimes called fairness), users can manage the number of active threads from within code.

This approach trades some software parallelism for a manageable number of threads. That seems reasonable, given that hardware parallelism is fixed and limited: the processor has a limited number of physical cores, so a much larger number of software threads simply adds thread-switching overhead.

In Theron the user is able to manage the tradeoff between parallelism and efficiency by explicit control over the number of worker threads, either at compile time or at runtime. Setting a higher number allows more heavyweight message handlers to be executed in parallel without starving other actors, at the expense of some extra context-switching.