实验 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.pyMini Ray 的 Actor 实现非常直接:
- 每个 Actor 一个线程。
- 每个 Actor 一个队列。
- 方法调用入队。
- Actor 线程按顺序取消息执行。
这个实现能帮助你理解 Ray Actor 的基本运行模型。
小练习
- 给
Counter增加decr方法。 - 创建 4 个 Counter Actor,把请求按
i % 4分发,模拟分片。 - 让
incr里time.sleep(1),观察单个 Actor 的队列瓶颈。
何时不用 Actor
不要为了“看起来面向对象”而使用 Actor。下面这些情况 Task 更简单:
- 计算没有共享状态。
- 输入输出都是不可变数据。
- 每次执行都互相独立。
- 你需要最大化横向并行度。