Skip to content

一、基于锁的并发方案及其局限

在单线程的面向对象程序设计中,对象的内部状态通过方法封装维护,方法调用按顺序执行,程序行为易于推理(可视作调用栈上的顺序执行)。当引入多线程后,多个线程可以同时调用同一对象的方法,原本的顺序语义被打乱,容易出现竞态(race condition)、不可预测的交互以及数据不一致。

一种常见的解决手段是使用互斥锁(mutex / synchronized 等)。锁能保证临界区在任一时刻只被一个线程执行,从而恢复顺序语义,但它也带来明显缺点:

  • 性能开销:锁(尤其是高争用情况下)会导致上下文切换、缓存同步、内存屏障等开销,降低并发吞吐量。

  • 阻塞与延迟:持有锁的线程若遇到阻塞操作会阻塞其他等待线程,影响延迟;相比协程/事件驱动模型,线程阻塞代价更高。

  • 死锁风险:设计不当可能产生死锁或活锁。

  • 扩展性差:分布式场景下需要引入分布式锁,通常依赖外部协调服务(如 ZooKeeper、etcd、数据库),其延迟和可用性问题会显著影响系统性能与可靠性。

总结:锁能解决共享状态的访问冲突,但代价高且在分布式环境下不理想。


二、CPU 缓存、内存和可见性问题(简要、准确说明)

现代多核 CPU 为了提高性能,会在每个核心或每个硬件线程上维护私有缓存(L1/L2 等)。内存一致性(memory consistency)和指令重排序等硬件/编译器优化,使得在多线程环境下:

  • 一个核心对某地址的写可能先停留在其缓存中,短时间内对其他核心不可见;

  • 编译器或 CPU 可能对指令执行顺序进行重排(在保证单线程语义的前提下),从而影响跨线程的可观测顺序;

  • 因此需要通过内存屏障、volatile(在 Java/Scala 中)或原子操作(CAS)来建立必要的“可见性”和“有序性”保证。

这些低层保证(volatile、原子类、内存屏障)能解决可见性和部分并发安全问题,但使用难度大、易出错,并且这些原语本身也会带来性能成本。


三、Actor 模型:核心思想与优势

Actor 模型不是依赖于共享内存的锁或原子操作,而是通过封装状态并以消息传递作为唯一的交互手段来管理并发。其核心要点如下:

  1. 封装 + 单线程处理语义(逻辑原子性)
    每个 Actor 拥有自己的局部状态,外部无法直接访问。Actor 只通过接收消息来变更其内部状态,且 Actor 在同一时刻只处理一条消息——这提供了一种“逻辑上的原子性”:处理一条消息期间,Actor 的内部状态不会被并发修改。注意:这种原子性是逻辑级别的(由运行时/调度器保证),并不是指令级或 CPU 原子指令的替代物。

  2. 异步消息传递
    Actor 之间通过发送消息互相通信。发送通常是非阻塞的(消息放入目标 Actor 的邮箱)。这样发送者不会因等待目标处理完成而阻塞,从而能将大量轻量任务分派到线程池上执行。

  3. 解耦执行与通信
    发送消息只是委托工作,接收者在合适的时间处理消息并可能回复。这样把“行为触发”与“行为执行”分离,便于横向扩展与分布式部署(消息也可用网络进行传输)。

  4. 高并发调度
    一个系统可以创建大量 Actor(数十万甚至更多),这些 Actor 不对应一一的线程,而由少量线程/线程池按需运行。这样能高效使用 CPU 资源,避免线程上下文切换带来的开销。

  5. 自然映射到分布式
    因为状态本地化且交互通过消息,Actor 的模型非常适合分布式部署:状态可以保存在单台机器上,跨机通信像本地消息一样由消息传输实现(虽涉及序列化与网络延迟,但模型概念保持一致)。


四、Actor 模型并不能“消除”共享资源等待的问题

如果多个参与者都需要访问同一个真实共享资源(例如某个外部数据库或文件),任何并发模型都必须处理同步/协调问题,等待与争用仍然存在。Actor 模型对这类问题的处理方式是将共享资源封装成唯一的 Actor(即把资源变为一个单例 Actor),所有对该资源的操作都以消息的形式发送到该 Actor,由它串行化处理请求。这样可以避免传统锁的使用并简化编程模型,但仍然存在排队延迟和吞吐瓶颈(因为请求被串行化)。因此需要在设计上权衡:

  • 是否可以通过分片、分区(sharding)将资源拆分为多个独立 Actor(减小单点瓶颈);
  • 是否可以将某些操作变为无锁的、可并行的操作(例如幂等读、多版本并发控制等);
  • 是否接受串行化带来的一致性语义(简化)与吞吐限制(成本)。

五、Actor 的组成要素(更精确地列出)

一个 Actor 至少需要以下几部分来支撑其运行语义:

  • 邮箱(mailbox):一个有序的消息队列,消息到达按队列顺序存放,运行时从中取出并交给 Actor 处理。不同实现可提供不同策略(优先级队列、批处理、限速等)。

  • 行为(behavior):用于处理消息的逻辑,包含 Actor 的内部状态和如何对不同类型消息做出反应。

  • 执行环境(dispatcher / scheduler):负责将准备执行的 Actor 与底层线程池/执行资源映射,保证 Actor 在任意时刻只由一个线程执行其消息处理逻辑。

  • 地址(ActorRef / PID):用于引用 Actor 的远程或本地句柄,发送者通过地址发送消息,不直接访问目标状态。

  • 消息(message):表示事件或请求的不可变数据结构(最好是不可变的,以简化并发与分布式传输)。

本站访客数 人次      本站总访问量