C++ Actor Model Pattern: Real-World Engineering Guide
C++ Actor Model Pattern: Real-World Engineering Guide
Problem Solved
Simplify concurrency by eliminating shared memory, using message passing between isolated actors.
How It Works
- “Actors” communicate by message passing
- Each actor processes messages sequentially
- No shared state between actors
- Actors can create other actors
STL Usage
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <memory>
#include <map>
#include <string>
#include <atomic>
using namespace std;
class Actor {
private:
queue<function<void()>> mailbox_;
mutex mtx_;
condition_variable cv_;
thread actor_thread_;
atomic<bool> running_{true};
void processMessages() {
while (running_) {
function<void()> message;
{
unique_lock<mutex> lock(mtx_);
cv_.wait(lock, [this]() { return !mailbox_.empty() || !running_; });
if (!running_ && mailbox_.empty()) break;
message = mailbox_.front();
mailbox_.pop();
}
message();
}
}
public:
Actor() : actor_thread_([this]() { processMessages(); }) {}
void send(function<void()> message) {
{
lock_guard<mutex> lock(mtx_);
mailbox_.push(message);
}
cv_.notify_one();
}
~Actor() {
running_ = false;
cv_.notify_one();
actor_thread_.join();
}
};
class ActorSystem {
private:
map<string, unique_ptr<Actor>> actors_;
public:
void createActor(const string& name) {
actors_[name] = make_unique<Actor>();
}
void send(const string& actor_name, function<void()> message) {
if (actors_.find(actor_name) != actors_.end()) {
actors_[actor_name]->send(message);
}
}
};
Example
#include <iostream>
using namespace std;
class CounterActor {
private:
Actor actor_;
int count_ = 0;
public:
void increment() {
actor_.send([this]() {
count_++;
});
}
void getCount(function<void(int)> callback) {
actor_.send([this, callback]() {
callback(count_);
});
}
};
void actorModelExample() {
CounterActor counter;
for (int i = 0; i < 100; ++i) {
counter.increment();
}
counter.getCount([](int count) {
cout << "Count: " << count << endl;
});
this_thread::sleep_for(chrono::milliseconds(100));
}
Use Cases
- Distributed systems: Akka, Erlang actors
- Concurrent systems: Isolated state management
- Game engines: Game entities as actors
- Microservices: Service communication
Key Takeaways
- No shared memory, eliminates many concurrency bugs
- Easy to reason about
- Scales to distributed systems
- Message passing is natural
Things to Be Careful About
- Message ordering: May not preserve global order
- Deadlocks: Circular message dependencies
- Performance: Message passing overhead
- Error handling: Actor failures need supervision
Summary
Actor model simplifies concurrency by eliminating shared state, making systems easier to reason about and scale.