Skip to content

实验 3:Actor 保存状态

Task 是无状态的。如果你需要一个远程对象持续保存状态,就要使用 Actor。

运行示例

bash
uv run python examples/ray_demos/03_actor_counter.py

代码:

python
@ray.remote
class Counter:
    def __init__(self) -> None:
        self.value = 0

    def incr(self, amount: int = 1) -> int:
        self.value += amount
        return self.value

    def get(self) -> int:
        return self.value

counter = Counter.remote()
refs = [counter.incr.remote() for _ in range(10)]
print(ray.get(refs))
print("final:", ray.get(counter.get.remote()))

观察点

输出通常是:

text
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
final: 10

这说明同一个 Actor 的 incr 调用按顺序修改了同一份状态。

Actor 为什么不是普通对象

普通 Python 对象只能在当前进程内使用。Ray Actor 是远程对象:

python
counter = Counter.remote()

这里返回的是 ActorHandle,不是 Counter 实例本身。方法调用也要用 .remote()

python
counter.incr.remote()

Actor 方法的返回值

Actor 方法调用仍然返回 ObjectRef

python
value_ref = counter.incr.remote()
value = ray.get(value_ref)

这意味着 Actor 方法可以和普通 Task 一样参与依赖图。

常见用法:模型服务

python
@ray.remote(num_gpus=1)
class ModelWorker:
    def __init__(self, path):
        self.model = load_model(path)

    def predict(self, batch):
        return self.model(batch)

模型只加载一次,多个请求复用这个 Actor。

Mini Ray 对照

bash
uv run python examples/mini_ray_runtime/demos/03_actor_counter.py

Mini Ray 的 Actor 实现非常直接:

  • 每个 Actor 一个线程。
  • 每个 Actor 一个队列。
  • 方法调用入队。
  • Actor 线程按顺序取消息执行。

这个实现能帮助你理解 Ray Actor 的基本运行模型。

小练习

  1. Counter 增加 decr 方法。
  2. 创建 4 个 Counter Actor,把请求按 i % 4 分发,模拟分片。
  3. incrtime.sleep(1),观察单个 Actor 的队列瓶颈。

何时不用 Actor

不要为了“看起来面向对象”而使用 Actor。下面这些情况 Task 更简单:

  • 计算没有共享状态。
  • 输入输出都是不可变数据。
  • 每次执行都互相独立。
  • 你需要最大化横向并行度。

面向学习目的的 Ray Core 中文导读与 Mini Ray 机制预览。