shell脚本通过&+wait实现并发,需控制并发数防过载,可用fifo、xargs -p或parallel等方案,并补充错误捕获、超时、信号处理和竞态防护。

Shell 脚本本身不支持多线程,但可以通过启动多个子进程实现并发效果。核心思路是让耗时任务在后台运行(&),再用 wait 统一收尾,从而把串行等待变成并行执行。关键不在“能不能并发”,而在于“如何控并发”——放任不管可能压垮系统,合理节制才能真正提效。
基础并发:& + wait 是最小可行组合
最简方式就是在循环体中每条命令后加 &,最后加一行 wait:
-
{} 包裹命令块:避免变量作用域或重定向混乱,比如
{ ping -c1 $ip >/dev/null && echo "$ip up"; } & - wait 不带参数:等待当前 shell 启动的所有后台子进程结束,适合任务数固定、资源压力可控的场景
- 注意:不加 wait,脚本会立刻退出,后台进程可能被 SIGPIPE 或 SIGHUP 中断(尤其远程 SSH 断连时)
控制并发数:避免资源过载的核心手段
一次性拉起几百个 ping 或 scp,容易触发系统限制(如 max user processes、file descriptors)或网络拥塞。必须限流:
-
有名管道(FIFO)+ 文件描述符:预写 N 个空行进管道,每次任务开始前
read -u6消费一行,结束再echo >&6归还,天然形成 N 个槽位 -
分批处理(batch + wait):比如每批跑 10 个,
for((i=0; i -
用 xargs -P:更简洁,
seq 1 254 | xargs -I {} -P 8 ping -c1 192.168.56.{} >/dev/null && echo "{} ok",-P 8 即严格控制最多 8 个并发
健壮性补充:别让一个失败拖垮全部
并发中单个任务出错不应导致整个流程中断,也不该掩盖问题:
-
错误捕获与日志分离:每个后台任务加
2>&1 | tee log-$$.txt或定向到独立文件,避免输出混杂 -
超时保护:用
timeout 5s ping -c1 $ip防止某个 IP 死等卡住整批 -
信号处理:加
trap 'kill -- -$$' INT TERM,确保 Ctrl+C 能终止所有子进程(包括子 shell 启动的) - 避免竞态:写共享文件时加
flock,或改用追加模式echo "$result" >> result.log(内核保证单次 write 原子性)
进阶选择:parallel 更省心但需安装
GNU parallel 功能完整,自带并发控制、结果顺序还原、失败重试等:
- 基础用法:
seq 1 100 | parallel -j 12 "sleep 1; echo {}",-j 12 指定并发数 - 自动负载感知:
parallel --load 1.0 -j+0表示 CPU 负载低于 1.0 时才启动新任务 - 比纯 Shell 实现少写 50 行胶水代码,适合长期维护脚本;若环境禁止安装,就用 xargs 或 FIFO 方案替代











