确保actor不共享状态的关键是所有状态变更仅在消息处理函数内由该actor单线程执行;禁止外部直接访问成员或调用方法,必须通过send()异步通信;邮箱起步用带锁std::queue即可;消息用std::variant而非虚基类;actorref需基于weak_ptr保障生命周期安全。

怎么让每个Actor真正“不共享状态”
关键不是把变量声明成private,而是确保所有状态变更**只发生在消息处理函数内部、且仅由该Actor自己的线程执行**。一旦你从外部直接调用actor->increment()或读取actor->count_,就等于绕过邮箱、撕开隔离墙——锁和竞态立马回来。
- Actor基类里不暴露任何数据成员,只提供
send()接口;子类状态全为private,行为封装在onMessage()或注册的处理器中 - 禁止在消息处理器里调用其他Actor的公有方法(比如
other_actor->do_something()),必须走other_actor->send(...) - 若需“读取”结果(如获取计数值),得用响应式设计:发
GetCount{std::promise<int>}</int>,对方处理完再promise.set_value(count_),调用方future.get()等待——这仍是异步通信,不是共享访问
邮箱(Mailbox)用什么容器?为什么别急着上无锁队列
新手常以为“无锁=高性能”,但moodycamel::ConcurrentQueue或自研无锁队列,在Actor场景下反而容易掩盖问题:它允许并发push/pop,可Actor本就不该被多线程同时消费——单线程循环pop才是语义正确性所在。
- 起步用
std::queue<:unique_ptr>></:unique_ptr>+std::mutex+std::condition_variable足够,锁只在入队时持有,极短 - 真正瓶颈不在邮箱吞吐,而在消息处理逻辑本身是否阻塞;与其折腾无锁,不如先检查
onMessage()里有没有sleep、std::fstream::read或没设超时的connect() - 若真到百万级消息/秒,再考虑无锁,但务必配合
std::atomic<bool> stopped_</bool>做退出同步——无锁队列常忽略“安全关闭”这个边界
消息类型怎么设计?std::variant比虚基类更稳
用class MessageBase { virtual ~MessageBase() = default; };加dynamic_cast看似面向对象,实则运行时开销不可控、异常路径难覆盖、还容易漏写clone()导致悬垂指针。
- 定义封闭消息集:
struct Inc {}; struct Get { std::function<void> reply; }; struct Stop {};</void>,然后using Message = std::variant<inc get stop>;</inc> - 发送时
actor->send(Inc{}),接收端用std::visit([](auto&& msg) { if constexpr (std::is_same_v<:decay_t>, Inc>) count_++; }, msg);</:decay_t>,编译期分发,零虚函数、零RTTI、零异常风险 - 大消息体(如10KB日志)才用
std::shared_ptr<const std::vector>></const>,小消息一律值传递;别为了“统一”把Inc也包进shared_ptr——这是典型过度设计
ActorRef不是装饰,是生命周期安全的刚需
裸new Actor()后传指针?崩溃只是时间问题。Actor销毁时若还有线程往它的邮箱里push,std::queue操作直接UB(未定义行为)。ActorRef本质是带引用计数的弱观察者。
立即学习“C++免费学习笔记(深入)”;
-
ActorRef内部持std::weak_ptr<actor></actor>,send()前先lock();失败即丢弃消息(或进死信队列),不崩溃 - 工厂函数返回
ActorRef,禁止用户new/deleteActor;析构Actor时自动清空邮箱、唤醒等待线程、join工作线程 - 跨线程传递
ActorRef完全安全——它只是个轻量句柄,不绑定线程,这点和std::shared_ptr一致
最容易被忽略的其实是消息边界:不是“发出去就算成功”,而是要确认对方邮箱已收、且Actor尚未停止。很多调试困难的“消息丢失”,根源都在ActorRef.lock()失败后静默丢弃,却没配死信监控。这层健壮性,得从第一个send()就写进去。










