本文目录导读:

在Shell脚本中实现并发执行控制,主要有以下几种常用方法,这里按照从简单到复杂的顺序介绍。
后台运行 + wait(最简单)
这是最基础的并发方式,使用 & 将任务放到后台,然后用 wait 等待所有完成。
#!/bin/bash
# 定义任务函数
task() {
local id=$1
echo "任务 $id 开始"
sleep $((RANDOM % 5 + 1))
echo "任务 $id 结束"
}
# 并发执行多个任务
for i in {1..5}; do
task $i &
done
# 等待所有后台任务完成
wait
echo "所有任务已完成"
优缺点:
- 优点:简单直接
- 缺点:无并发数量限制,可能瞬间启动过多进程导致系统负载过高
信号量控制(限制并发数)
使用 mkfifo 创建命名管道实现信号量,控制同时运行的进程数量。
#!/bin/bash
# 并发控制函数 - 使用命名管道作为信号量
concurrent_control() {
local max_concurrent=$1 # 最大并发数
local task_file=$2 # 任务列表文件
# 创建临时FIFO(命名管道)作为信号量
local fifo_file=$(mktemp -u)
mkfifo $fifo_file
exec 3<>$fifo_file
rm -f $fifo_file
# 初始化信号量:向管道写入max_concurrent个令牌
for ((i=0; i<$max_concurrent; i++)); do
echo >&3
done
# 读取任务并执行
while IFS= read -r task; do
# 从管道读取令牌(无令牌时会阻塞)
read -u 3
# 启动任务
(
echo "执行任务: $task"
eval "$task" # 实际执行任务
echo "任务完成: $task"
# 任务结束后归还令牌
echo >&3
) &
done < "$task_file"
# 等待所有任务完成
wait
# 清理
exec 3>&-
}
# 使用示例
cat > /tmp/tasks.txt << EOF
sleep 2; echo "任务1"
sleep 3; echo "任务2"
sleep 1; echo "任务3"
sleep 4; echo "任务4"
sleep 2; echo "任务5"
sleep 3; echo "任务6"
EOF
# 限制最多同时运行3个任务
concurrent_control 3 /tmp/tasks.txt
xargs 实现并发控制(最推荐)
xargs 的 -P 参数可以非常方便地控制并发数。
#!/bin/bash
# 定义要执行的任务
task() {
local id=$1
echo "任务 $id 开始 (PID: $$)"
sleep $((RANDOM % 5 + 2))
echo "任务 $id 结束"
}
# 导出函数,使xargs可以访问
export -f task
# 生成任务参数列表
seq 1 10 | xargs -I {} -P 5 bash -c 'task "$@"' _ {}
echo "所有任务完成"
更实用的示例:
#!/bin/bash
# 批量下载文件,最多3个并发
download_file() {
local url=$1
local filename=$(basename $url)
echo "开始下载: $filename"
wget -q $url -O /tmp/$filename
echo "下载完成: $filename"
}
export -f download_file
# 从文件读取URL列表,并发3个下载
cat urls.txt | xargs -I {} -P 3 bash -c 'download_file "$@"' _ {}
GNU parallel(功能最强大)
如果系统安装了 parallel 命令,这是处理并发任务的最佳选择。
#!/bin/bash
# 安装GNU parallel(如果未安装)
# Ubuntu/Debian: apt-get install parallel
# CentOS/RHEL: yum install parallel
# 基本使用
task() {
local id=$1
echo "任务 $id"
sleep 2
echo "任务 $id 完成"
}
export -f task
# 并发5个任务
parallel -j 5 task ::: {1..20}
# 从文件读取参数
parallel -j 3 -a params.txt task
# 在处理命令时保留输出顺序
parallel --keep-order -j 4 task ::: {1..12}
GNU parallel的高级特性:
#!/bin/bash
# 分布式执行
parallel --sshlogin server1,server2 -j 2 task ::: {1..10}
# 错误处理
parallel --halt now,fail=1 -j 3 task ::: {1..10}
# 进度显示
parallel --progress -j 4 task ::: {1..100}
# 结果保存
parallel --results output/ -j 4 task ::: {1..10}
完整的生产环境示例
结合了多种技术的实际应用:
#!/bin/bash
# 生产环境的并发任务管理器
set -euo pipefail
# 配置
MAX_CONCURRENT=4
LOG_DIR="/var/log/concurrent_tasks"
PID_FILE="/tmp/concurrent_tasks.pid"
# 日志函数
log() {
local level=$1
local msg=$2
echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $msg"
}
# 任务函数
process_item() {
local item=$1
local task_id=$2
log "INFO" "【任务$task_id】开始处理: $item"
# 模拟处理时间
sleep $((RANDOM % 5 + 2))
# 模拟失败情况
if [ $((RANDOM % 10)) -eq 0 ]; then
log "ERROR" "【任务$task_id】处理失败: $item"
return 1
fi
log "INFO" "【任务$task_id】处理成功: $item"
}
# 主函数
main() {
local task_list=("数据A" "数据B" "数据C" "数据D" "数据E" "数据F" "数据G")
local total=${#task_list[@]}
local success_count=0
local fail_count=0
# 创建日志目录
mkdir -p "$LOG_DIR"
log "INFO" "开始并发处理 $total 个任务,最大并发数: $MAX_CONCURRENT"
# 导出函数和变量
export -f process_item log
export LOG_DIR
# 使用xargs控制并发
for i in "${!task_list[@]}"; do
echo "${task_list[$i]} $i"
done | xargs -n 2 -P $MAX_CONCURRENT bash -c 'process_item "$1" "$2"' _
log "INFO" "所有任务处理完成"
}
main "$@"
选择建议
- 简单场景(并发数少,不敏感):使用
&+wait - 需要控制并发数:使用
xargs -P或命名管道 - 复杂场景(错误处理、分布式):使用
GNU parallel - 脚本可移植性重要:使用命名管道方式
建议:对于大多数生产环境,xargs -P 是最平衡的选择——既简单又功能完整。