平常写shell脚本都是单个进程执行,串行执行程序,如果循环体中的逻辑与上下循环没有关联,那么可以采用多进程执行循环
类似于进程池的效果,实现代码如下:
#!/bin/bash
#线程池
process_pool(){
#判断输入参数等
if [ $# -lt 3 ]; then
echo "$0 process_num command [args]"
return 1
fi
_process_num=$1
shift
_func=$1
shift
if [[ ! $_process_num =~ ^[0-9]+$ ]]; then
echo "process_num must be a number"
return 1
fi
if !type $_func >/dev/null 2>&1; then
echo "comannd must be executable"
return 1
fi
# 创建一个先进先出的管道文件
fifo="/tmp/$$.fifo"
mkfifo $fifo
#创建一个文件描述符号,把FD这个文件描述符关联到这个文件
#{FD}表示非显示的描述符
exec {FD}<>$fifo
rm $fifo
# 创建槽位
for i in $(seq $_process_num); do
echo >&$FD
done
# 执行具体命令
for arg in $@; do
read -u $FD
{
$_func $arg
echo >&$FD
}&
done
# wait等待所有后台进程执行完成
wait
# 释放文件描述符
exec {FD}>&-
}
#以下为测试
test(){
echo $1
sleep 3
return 0
}
process_pool 3 'test' 1 2 3 4 5 6 7
第二种写法:
原始串行写法
#!/bin/bash
START_TIME=`date +%s`
for ((I=1;I<=10;I++)) #模拟10个任务
do
echo "${I}: success" ; sleep 2
done
STOP_TIME=`date +%s`
echo "TIME: $(expr ${STOP_TIME} - ${START_TIME})"
并行写法1:后台执行
#!/bin/bash
START_TIME=`date +%s`
for ((I=1;I<=10;I++))
do
{
echo "${I}: success" ; sleep 2
}&
done
wait
STOP_TIME=`date +%s`
echo "TIME: $(expr ${STOP_TIME} - ${START_TIME})"
并行写法2:管道+文件描述符
#!/bin/bash
THREAD_NUM=5
TMP_FILE="/tmp/$$.fifo"
trap "exec 6>&-;exec 6<&-;exit 0" 2 #脚本运行过程中,如果接收到信号2(Ctrl+C)中断命令,则关闭文件描述符6的读写,并正常退出
mkfifo ${TMP_FILE} #创建有名管道
exec 6<>${TMP_FILE} #创建文件描述符,文件描述符可使用3-(n-1),n取值范围:ulimit -n。以读写(<,读;>,写)方式绑定TMP_FILE管道文件。标识对文件描述符6的所有操作等同于对管道文件TMP_FILE的操作
rm -rf ${TMP_FILE} #为什么不直接使用管道文件?因为管道的一个重要特性:读写必须同时存在,缺失某个操作,另一个操作就会滞留。绑定文件描述符(读、写绑定)正好解决了这个问题
for ((J=1;J<=${THREAD_NUM};J++)) #向管道中中输入THREAD_NUM个并发数量的空行。为什么写入空行而不是字符?那是因为管道文件的读取是以行为单位。
do
echo >&6
done
START_TIME=`date +%s`
for ((I=1;I<=10;I++))
do
read -u6 #从管道中读取行,每次读一行。每读一次就会减少一个空行,直到管道中没有回车符,所有行读取完毕后执行挂起,实现线程数量控制。
{
echo "${I}: success" ; sleep 2
echo >&6 #任务在后台执行结束后,向文件描述符中写入一个空行。如果不在向描述符中写入空行,当后台放入THREAD_NUM个任务之后,由于描述符中没有可读取的空行,会导致read -u6停顿。
}&
done
wait
STOP_TIME=`date +%s`
echo "TIME: $(expr ${STOP_TIME} - ${START_TIME})"
exec 6>&-
exec 6<&-
|