写批量脚本的时候,你有没有遇到过这种两难:并行数设低了效率上不去,设高了服务器又扛不住。用 bash 的 FIFO 加 wait -n 搭一个令牌桶,就能把这事解决得很漂亮。
什么是令牌桶?为什么它适合做并发控制
令牌桶的核心逻辑很简单:桶里有固定数量的"令牌",每个任务启动前必须从桶里取走一个令牌,任务结束后把令牌还回去。这样同时运行的任务数量永远不会超过桶的容量。换成 bash 的说法就是:创建 N 个 FIFO,启动任务前从某个 FIFO 读一把,任务结束时往另一个 FIFO 写一把。整个过程不需要任何第三方工具,bash 原生就能跑。
方法一:双 FIFO 方案(最常用)
一端放令牌,另一端收令牌,逻辑最清晰。
这层的关键在于:read < $TOKEN_FIFO 会一直阻塞,直到桶里有空闲令牌。这样即使一口气提交 10 个任务,实际同时跑的也只有 4 个。
方法二:单 FIFO 加超时控制(更简洁)
令牌直接记录活跃槽位,一个 FIFO 搞定一切。
注意这里用了 read -t 1 加 1 秒超时,目的是让主循环不会在所有槽位都空着时永久阻塞。实际使用中根据任务预估耗时调整超时时间即可。
方法三:用 exec 9 打开读写双端(高级写法)
把 FIFO 同时以读写方式打开到文件描述符 9,让它成为一个永不阻塞的令牌管道。
用 exec 9<>$FIFO 打开读写端后,FIFO 本身就有读端和写端,所以永远不会报"FIFO 没有读者"而阻塞。适合需要更精确控制的复杂场景。
三种方案横向对比
实际选哪个,取决于你的场景。
- 双 FIFO 方案:逻辑最清晰,调试最容易,适合大多数批量任务场景。
- 单 FIFO 加超时:代码最简洁,适合任务耗时稳定、可以预估超时时间的场景。
- exec 读写双端:控制最精确,适合对性能要求高、需要在任务内部精确管理令牌归还的场景。
并发数怎么定
令牌桶的容量不是拍脑袋定的,有个参考公式:并发数 ≈ CPU 核心数 × 2(适用于 I/O 密集型任务)。如果是纯 CPU 计算,则保持在核心数左右。设完之后观察系统负载和任务总耗时,找到最优值。
还有个细节:网络请求类任务别只看系统负载,还要关注对方服务的限流阈值。令牌桶能控制本地并发,但发请求之前最好查清楚目标 API 的 QPS 上限。
写在最后
FIFO 令牌桶的好处是整个机制一目了然:桶里有多少令牌,同时就在跑多少任务。不需要装任何依赖,纯 bash 原生实现。如果你现在用的是 sleep 循环来控并发,或者干脆不控并发导致服务器爆掉,换成这个方案会稳很多。
实际项目中,令牌桶往往会配合 trap 一起用,确保脚本被中断时 FIFO 能被正确清理,不会留下垃圾文件。
相关推荐
版权声明
本文仅代表个人观点。
本文系AI辅助作者原创,未经许可,转载请保留原文链接。

发表评论