侧边栏壁纸
博主头像
一笑痕

人生若只如初见,
是可喜亦或者是可悲?

  • 累计撰写 265 篇文章
  • 累计收到 7 条评论

工具并发上限为二,五个任务仍按请求顺序回收

2026-11-11 / 0 评论 / 7 阅读

工具并发上限为二,五个任务仍按请求顺序回收

五个工具调用同时到达,不代表五个都该立刻启动。若每个工具都会占一个连接或触发一次昂贵查询,Promise.all(calls.map(runTool)) 就把这一轮的峰值并发直接设成调用数。Agent 的「并行」需要一个上限,也需要说清楚这个上限管的是执行中的任务,还是整个队列。

这里用本机 Node 写了五个固定任务,延迟分别是 12、2、2、2、2 毫秒,把并发上限设成 2。耗时只是让先后关系可观察,不是性能基准。测的是调度逻辑,不调用模型,也不声称不同机器上的时间会一样。

峰值被限住,队列仍按输入推进

脚本启动两个 worker。每个 worker 取下一个索引,执行完当前任务后再取下一个;结果写回原索引。跑完后断言活跃数没有超过上限,并检查启动顺序和回收顺序。

jobs=5
limit=2
peak=2
start_order=A,B,C,D,E
finish_order=B,C,D,E,A
result_order=A,B,C,D,E

任务 A 比后面四个都慢,但它先被取出。B 结束后,空出的 worker 继续拿 C;另一条 worker 也依次领取 D、E。完成顺序因此和输入顺序不同,结果数组却按输入索引排列。对工具调用来说,这种区别不只是日志好不好看:返回结果必须仍然对应原来的调用 ID,不能把「先完成」误当成「先请求」。

并发数字说的是同时运行数

上限为 2 时,排队中的任务不计入活跃数。若所有任务各自再派生子任务,或者工具内部又建立并发请求,外层的 2 并不能约束全系统的总负载。这个实现只有一个调度层,现实里的边界要由工具本身配合,或者把并发额度放到共享调度器里统一管理。

串行 for...of 每次只启动一个任务,峰值自然是 1,但会让后面的快任务等待队头的慢任务。无上限的 Promise.all 启动所有任务,队列等待少了,峰值却随调用数增长。固定 worker 数的写法夹在中间:任务仍按输入顺序领取,执行可以重叠,结束时间不保证有序。

限流宜放在工具执行入口,别让每个工具各自猜一个并发数。否则同一轮的三个工具可能各自以为「最多两个」,合起来却同时跑六个。若工具有优先级或取消语义,FIFO 也未必合适;这段脚本没有验证优先级、公平性或中途取消,不能拿它们当已解决的问题。

先保护外部副作用

并发上限只管容量,不会让副作用自动安全。两个写工具仍可能互相覆盖;一个任务超时后,队列也不会凭空知道底层工作已经停止。启动前仍需校验参数和授权,完成后还要按调用 ID 回填结果。若超时后允许重试,工具本身还得能处理重复请求。

因此,适合的默认值是一个明确、可观察的额度。读取类工具可以从小上限开始,再按服务承载调整;有写入副作用的工具不能只靠限流防重。模型本身未实测,这里的结论只覆盖本机固定任务驱动的 Node 调度器。

    🤞 分享