Appearance
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 / brokerasyncio.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 process | stale writer 仍被 DB version/lease 拒绝 |
自测
- 一个 asyncio.Lock 能否保护另一个进程里的共享状态?先区分 coroutine、thread、process 的内存边界。
- 把整个 Tool 执行都包在大锁里虽然“安全”,为什么可能把并发能力降成串行?锁应只覆盖什么临界区?