在日常数据处理过程中,很多时候用的都是单进程处理;如果任务较大的时候,这样耗时比较久同时也浪费资源,可将任务多进程并发处理,并合理控制其进程数,将很有效的利用机器资源、减少时间。经过资料查询与实践,这里脚本的功能就是产生多进程并控制其并行进程数量。具体多进程的好处可以参考其他资料,这里就不细说了。
并行进程数超过任务总数是没有意义的,因为实际并行总数会小于等于任务总数;
二、应用场景
比如抓取网页数据,比如需要抓取100w个网页的数据,将其数据过滤后存入数据库,如果采用单进程或者手工操作肯定是很麻烦耗时的,因此,采用这种方式会很好的提高效率。但是,如果循环处理的子任务耗时过短,如:只有一个命令等,那就没有必要控制多进程,因为那样就增加了运行时间,但是一般单个任务处理时间超过1s且任务量比较大时,我个人认为多进程控制还是很有必要。
其他:批量数据文件导入数据库、数据库数据批量导出为多个文件也可以使用这种方式;
三、使用说明
- 如果要利用模板脚本处理任务,那么只需要在下图红色处按要求添加自己的任务处理即可(后续测试实例简单地处理了一下模板),具体源码与注释见附件(运行环境Linux bash);
<<多进程并发与控制脚本-处理流程图>>
![](https://i-blog.csdnimg.cn/blog_migrate/a21e9cc5581988aa8eb55bcfb9cd00d3.jpeg)
四、测试实例
1. 我自己根据模板脚本写了一个测试例子;将指定规则的文本信息输出到1000个文件中,共有10000条记录,输出后睡眠3s(用于测试),控制进程为1000个。主要是验证是否能够达到多进程及其控制的效果。在里面处理日志的时候还加入了一些文件描述符重定向有关的东西。
2. 源码见附件(运行环境Linux bash)
注:说得比较详细;实际操作过程中可简化代码,实现功能!
由于无法附件,源码:
模板_MultiProcess.sh
##################
#简单说明:
# 多进程控制利用读取管道文件fifo实现并发进程数量的控制;
# 运行:$1 并行进程数
##################
#!/bin/bash
####目录/文件变量设置###################
TmpFifoFile=~/tmp/$$.fifo #临时管道文件
################################
#防止异常爆发
set -eo pipefail
###########自定义函数区域BEGIN##########
#########
#此处定义需要处理的函数逻辑[ 说明 ]
#
#while/for...任务逻辑
#一个read -u6命令执行一次,就从fd6中减去一个回车符,然后向下执行,
#fd6中没有回车符的时候,就停在这了,从而实现了进程数量控制
#read-u6 #读取令牌
#do
#(
#function循环子任务处理函数(此处一般不再开立子进程处理)
#当任务处理完成后,再向fd6中加上一个回车符,即补上了read -u6减去的那个,子进程>会继承父
#进程的文件描述符
#echo >&6释放令牌
#)&
#done
#wait
##########
function deal_task()
{
return 0
}
#######自定义函数区域END#######
#创建并行进程令牌
#目前令牌用的文件符6
function create_token()
{
proc_num=$1 #令牌数
#获取当前登录用户最大可用进程
UserMaxUseProcNum=$(expr $(ulimit -u) - $(ps -ef|awk -F " " '{print $1}'|grep $(whoami)|wc -l))
if [ $# -ne 1 ]
then
echo "USAG: $0 并行进程数(当前用户可用${UserMaxUseProcNum},建议不超过1/3,实际最大进程数为所有子任务数)"
return 1
fi
#判别输入并行进程数是否符合要求)
test=1
if [ ${proc_num} -gt ${UserMaxUseProcNum} ]
then
echo "ERROR!输入进程总数${proc_num}大于当前用户可用进程总数${UserMaxUseProcNum}"
return 1
fi
#建立临时管道用于控制并发进程数
tmp_fifofile="/tmp/$$.fifo"
mkfifo $tmp_fifofile
exec 6<>$tmp_fifofile # 将fd6指向fifo文件,设置读写
rm $tmp_fifofile #清除管道文件
#根据传入的进程数量定义令牌数
for ((i=1;i<=${proc_num};i++))
do
echo
done >&6
return 0
}
#####主进程设置################
echo "脚本运行--开始----`date`-------------"
(
echo "**创建令牌 begin**"
#传入进程数量
create_token $1 ||{
echo "创建进程令牌失败"
exit 1
}
echo "**创建令牌 end**"
echo "**任务处理 begin**"
deal_task ||{
echo "任务处理失败!"
exit 1
}
echo "**任务处理 end**"
)&
#等待任务处理完毕
wait
exec 6<&- #释放文件描述符
echo "脚本运行--结束----`date`-------------"
实例MultiProcess_CreateFile.sh
##################
#简单说明:
# 多进程控制利用读取管道文件fifo实现并发进程数量的控制;
# 运行:$1 并行进程数
# 功能:
# 测试生成10000条记录,共1000个文件,最大并行进程数为1000
##################
#!/bin/bash
####目录/文件变量设置###################
TmpFifoFile=~/tmp/$$.fifo #临时管道文件
OutDataPath=./data #输出文件路径 by yuanwm
OutLogPath=./log #输出日志文件 by yuanwm
errlogname=$$.errlog #错误日志文件 by yuanwm
runlogname=$$.runlog #运行日志文件 by yuanwm
################################
#防止异常爆发
set -eo pipefail
###########自定义函数区域BEGIN##########
#########
#此处定义需要处理的函数逻辑[ 说明 ]
#
#while/for...任务逻辑
#一个read -u6命令执行一次,就从fd6中减去一个回车符,然后向下执行,
#fd6中没有回车符的时候,就停在这了,从而实现了进程数量控制
#read-u6 #读取令牌
#do
#(
#function循环子任务处理函数(此处一般不再开立子进程处理)
#当任务处理完成后,再向fd6中加上一个回车符,即补上了read -u6减去的那个,子进程>会继承父
#进程的文件描述符
#echo >&6释放令牌
#)&
#done
#wait
##########
function deal_task()
{
#define by yuanwm
#测试生成10000条记录,共1000个文件
for((i=0;i<10000;i++))
do
read -u6
(
echo "处理任务 ${i}"
echo "编号$i|测试企业名称$i|测试联系人$i|测试联系人号码$i" >> ${OutDataPath}/test_data_`expr $i % 1000 `.txt
sleep 3
echo >&6
)&
done
wait
return 0
}
#######自定义函数区域END#######
#创建并行进程令牌
#目前令牌用的文件符6
function create_token()
{
proc_num=$1 #令牌数
#获取当前登录用户最大可用进程
UserMaxUseProcNum=$(expr $(ulimit -u) - $(ps -ef|awk -F " " '{print $1}'|grep $(whoami)|wc -l))
if [ $# -ne 1 ]
then
echo "USAG: $0 并行进程数(当前用户可用${UserMaxUseProcNum},建议不超过1/3,实际最大进程数为所有子任务数)"
return 1
fi
#判别输入并行进程数是否符合要求)
if [ ${proc_num} -gt ${UserMaxUseProcNum} ]
then
echo "ERROR!输入进程总数${proc_num}大于当前用户可用进程总数${UserMaxUseProcNum}"
return 1
fi
#建立临时管道用于控制并发进程数
tmp_fifofile="/tmp/$$.fifo"
mkfifo $tmp_fifofile
exec 6<>$tmp_fifofile # 将fd6指向fifo文件,设置读写
rm $tmp_fifofile #清除管道文件
#根据传入的进程数量定义令牌数
for ((i=1;i<=${proc_num};i++))
do
echo
done>&6
return 0
}
#####主进程设置################
echo "脚本运行--开始----`date`-------------"
#目录创建
mkdir -p ${OutDataPath}
mkdir -p ${OutLogPath}
#日志重定向
exec 5<&1 #标准输出保存
exec 7<&2 #标准错误保存
exec 1>>${OutLogPath}/${runlogname}
exec 2>>${OutLogPath}/${errlogname}
(
echo "**创建令牌 begin**"
#传入进程数量
create_token $1 ||{
echo "创建进程令牌失败"
exit 1
}
echo "**创建令牌 end**"
echo "**任务处理 begin**"
deal_task ||{
echo "任务处理失败!"
exit 1
}
echo "**任务处理 end**"
)&
#等待任务处理完毕
wait
exec 1<&5 #标准输出还原
exec 6<&- #释放文件描述符
exec 5<&- #释放文件描述符
exec 7<&- #释放文件描述符
echo "脚本运行--结束----`date`-------------"