Skip to content
预计 1 分钟右侧目录可快速定位

Harness 共享状态怎么保护:Lock、Coroutine、Thread 与 Process ​

前置:Harness 内部任务如何排队而不失控:asyncio.Queue、Producer / Consumer 与 Race

最危险的不是“没加锁”,而是锁错了层 ​

例如 Scheduler 维护一个进程内 ready_nodes 集合。两个 coroutine 同时修改它,可以用 asyncio.Lock 保护很短的内存临界区。

但下面这种做法会把整个 Runtime 卡住:

python
async with lock:
    result = await model.generate(request)   # 秒级甚至分钟级
    await tool.execute(result.tool_call)

Lock 持有期间所有等待同一锁的工作都无法推进。远程 I/O 不应该包含在共享内存临界区里。

先问“谁共享”,再选同步原语 ​

text
同一 event loop 的 coroutine
→ asyncio.Lock / Queue / 单 owner

同一进程中的 OS thread
→ threading primitive + thread-safe boundary

不同 process / 不同 machine
→ DB CAS / lease / Redis atomic primitive / broker

asyncio.Lock 只在一个 event loop/进程内有意义。它不能阻止另一台 Worker 更新同一个 Run。

Harness 更优先使用 Ownership 而不是到处加 Lock ​

与其多个 Task 直接修改:

text
RunState
Tool results
ready set
stream buffer

更清晰的设计通常是:

text
多个 producer
   ↓ message/result
单一 Runtime reducer / state owner
   ↓
new immutable/versioned state

这样很多 race 直接消失。真正持久化时再用 optimistic version/CAS 保证 stale writer 不能覆盖新状态。

Thread / Process / Coroutine 分别解决什么 ​

Coroutine:大量 HTTP/Model/DB 等 I/O 等待,切换成本低,是 Harness 主路径。

Thread:包装无法异步化的阻塞 SDK、文件系统调用或 C 扩展。它主要用于避免阻塞 event loop,不意味着所有 Python CPU 工作都会获得线性多核加速。

Process:CPU-heavy 解析、图算法、局部模型/大规模压缩等需要真正隔离 CPU 时使用;代价是序列化、进程通信和生命周期管理更复杂。

这些都是本机执行模型。生产中的 Run Worker 横向扩展属于 Module 09/14,不要把 multiprocessing 当 durable scheduler。

Failure Modes ​

持有 Lock 跨 Model/Tool I/O ​

后果: 一个慢 Provider 把无关 Run 一起阻塞。

处理: 锁内只复制/更新小块内存状态,释放后再 await;回来后用 version 检查合并。

用 Python Lock 保护数据库中的 Run ​

后果: 多实例部署后锁完全失效。

处理: durable fact 使用 transaction/CAS/lease/fencing。

如何验证 ​

场景必须成立
Provider 人为 sleep 10 秒无关 Run 的内存操作不被同一 Lock 阻塞
启动两个 service processstale writer 仍被 DB version/lease 拒绝

自测 ​

  1. 一个 asyncio.Lock 能否保护另一个进程里的共享状态?先区分 coroutine、thread、process 的内存边界。
  2. 把整个 Tool 执行都包在大锁里虽然“安全”,为什么可能把并发能力降成串行?锁应只覆盖什么临界区?

Sources ​

AI Engineering · Agent · Harness · Backend Systems