一、引言
在日常运维、批处理、RPA、数据处理场景中,我们经常会遇到这样的问题:
- 有一堆任务要执行,但不能一次性全跑,
- 需要限制并发数,同时保证:
- 任务异常不会影响整体调度
- 并发资源不会“泄漏”
- 用户只关心写任务,不关心调度细节
很多人会第一时间想到 xargs -P、& + wait,但一旦任务复杂、失败场景变多,这些方式就会逐渐失控。
本文介绍一种纯 Bash、工程级的并发任务调度方案,核心思想是:
- 用 FIFO 实现令牌桶,用 trap 保证资源一定释放
二、设计目标
我们希望这个并发执行框架具备以下特性:
- 可控并发数(最大同时运行 N 个任务)
- 任务失败不影响整体调度
- 异常情况下不丢令牌、不死锁
- 调度逻辑与业务任务解耦
- 用户接口极简
最终结构如下:
.
├── run.sh # 调度框架(用户无需修改)
└── tasks.sh # 用户任务(用户只改这个)
三、核心原理:FIFO + 文件描述符 = 令牌桶
3.1 为什么选 FIFO
Linux 的 FIFO(命名管道)有一个非常重要的特性:
- read 会阻塞,直到有数据可读
这正好可以用来模拟“令牌”。
3.2 令牌桶模型
- FIFO 中每一行数据 = 1 个令牌
- 启动时往 FIFO 里写入 N 个令牌
- 每个任务开始前 read 一个令牌
- 任务结束后 echo 一个令牌回去
并发数天然被限制在 N。
四、调度框架实现(run.sh)
4.1 初始化令牌桶
TOKEN_NUM=4
FIFO=/tmp/token_bucket.fifo
mkfifo "$FIFO"
exec 9<>"$FIFO"
rm -f "$FIFO"
for ((i=0; i<TOKEN_NUM; i++)); do
echo >&9
done
- 使用 fd 9 专门作为令牌桶
- FIFO 文件本身删除,但 fd 仍然有效(经典用法)
- 初始化并发数为4个并发,配置项:TOKEN_NUM
4.2 为什么必须用 trap
很多人会问:
- 用户命令崩溃了,shell 不是也会退出吗?
- 为什么还要用 trap?
关键原因只有一个:
- 很多异常路径,根本走不到“释放令牌”的那一行代码。
比如:
- set -e 下命令返回非 0
- 用户命令主动 exit
- exec 替换 shell
- 脚本被 SIGTERM / SIGINT 中断
一旦少释放一个令牌,结果就是:
- 并发度永久 -1,最终系统“假死”
正确的做法是:
- trap 'echo >&9' EXIT
无论怎么退出,令牌一定归还。
这在 shell 世界里,相当于:C++ 的 RAII。
4.3 单个任务执行模型
__run_one_task() {
local name=$1
shift
read -u9 # 获取令牌
{
trap 'echo >&9' EXIT
echo "[$(date '+%Y-%m-%d %H:%M:%S')] START $name"
"$@"
echo "[$(date '+%Y-%m-%d %H:%M:%S')] END $name"
} &
}
4.4 提供给用户的接口
run_task() {
local name=$1
shift
__run_one_task "$name" "$@"
}
用户永远不直接接触 FIFO / fd。
五、任务注册机制(tasks.sh)
用户只需要实现一个register_tasks函数:
register_tasks() {
run_task "task-1" sleep 2
run_task "task-2" bash -c '
echo "doing something"
sleep 1
'
for i in {3..10}; do
run_task "loop-$i" sleep 2
done
}
在register_tasks函数中,用户需要调用run_task接口,将业务任务注册进来。用法如下:
run_task [task name] [task command]
用户在register_tasks函数中将所有任务注册进调度程序中,调度程序按照设定好的并发度执行这些任务。
六、这种方案适合哪些场景
- 批量运维任务
- RPA 自动化
- 数据处理 / 离线任务
- CI / 发布流水线
- 需要严格并发控制的脚本系统
优点:
- 无第三方依赖
- 行为可预测
- 异常不死锁
- 易于扩展
七、获取完整脚本
📌 获取方式如下:
- 关注我的微信公众号:Hankin-Liu的技术研究室
- 发送关键词:parallel task
- 获取脚本
- 发送关键词:”性能调优群“或"C++群“,加入感兴趣的技术交流群,可免费解答和交流技术问题。
八、总结
并发脚本并不是少见需求,在批量任务、自动化运维、数据处理等场景中被广泛使用。相比引入复杂系统,一个设计正确的并发 Shell 脚本,往往就能以极低成本解决问题。
本文通过令牌桶模型,给出了一种并发可控、异常安全、长期稳定的并发执行方式。
其价值不仅在于精确控制同时执行的任务数量,更在于即使任务失败或异常退出,并发资源也不会被泄露。
在结构上,将并发调度与业务逻辑解耦:
- 并发控制统一封装
- 业务任务按接口注册
- 框架可复用,任务可替换
这种方式使脚本既适合一次性使用,也能沉淀为长期复用的工具。
📬 欢迎关注VX公众号“Hankin-Liu的技术研究室”,持续分享信创、软件性能测试、调优、编程技巧、软件调试技巧相关内容,输出有价值、有沉淀的技术干货。