首先附上自己常用的代码

------------------------------------------------------------------------------------------------------------------------------------
#定义并发进程数量
PARALLEL=xx

#定义临时管道文件名
TMPFILE=$$.fifo

#定义导出配置文件全路径名
CMD_CFG=${xx}

#定义失败标识文件
FAILURE_FLAG=failure.log

####################### 函数定义 ########################
# 中断时kill子进程
function trap_exit
{
kill -9 0
}

# 通用执行函数
exec_cmd()
{

# 此处为实际需要执行的命令,本例中用sleep做示例

xxxxxx

if [ $? -ne 0 ]
then
echo "命令执行失败"
return 1
fi
}
trap 'trap_exit; exit 2' 1 2 3 15

#清理失败标识文件
rm -f ${FAILURE_FLAG}

#为并发进程创建相应个数的占位
mkfifo $TMPFILE
exec 4<>$TMPFILE
rm -f $TMPFILE
{
count=$PARALLEL
while [ $count -gt 0 ]
do
echo
let count=$count-1
done
} >&4

#从任务列表 seq 中按次序获取每一个任务
while read SEC
do
read <&4
( exec_cmd ${SEC} || echo ${SEC}>>${FAILURE_FLAG} ; echo >&4 ) &
done<$CMD_CFG
wait
exec 4>&-

#并发进程结束后判断是否全部成功
if [ -f ${FAILURE_FLAG} ]
then
echo "没有全部成功"
else
echo "全部成功"
fi

-----------------------------------------------------------------------------------------------------------------------------------------------------------

在Linux下运行作业时, 经常会遇到以下情形: 有大量作业需要运行, 完成每个作业所需要的时间也不是很长. 如果我们以串行方式来运行这些作业, 可能要耗费较长的时间; 若采用并行方式运行则可以大大节约运行时间. 再者, 目前的计算机绝大部分都是多核架构, 要想充分发挥它们的计算能力也需要并行化. 总结网上看到的资料, 利用Bash脚本, 可以采用下面几种方法实现批量作业的并行化. 注意, 下面论述中将不会区分进程和线程, 也不会区分并行和并发.

1. 采用GNU的paralle程序

parallel是GNU专门用于并行化的一个程序, 对于简单的批量作业并行化非常合适. 使用parallel不需要编写脚本, 只需在原命令的基础上简单地加上parallel就可以了. 所以, 如果能用paralle并行化你的作业, 请优先使用. 有关paralle的详细说明, 请参考其官方文档.

2. 最简单的并行化方法: &+wait

利用Bash的后台运行&wait函数, 可实现最简单的批量作业并行化. 如下面的代码, 串行执行大约需要10秒

# Language: bash
for((i=1; i<=3; i++)); do {
sleep 3
echo "DONE!"
} done

改为下面的简单并行代码理想情况下可将运行时间压缩到3秒左右

# Language: bash
for((i=1; i<=3; i++)); do {
sleep 3
echo "DONE!"
} & done
wait
3. 进程数可控的并行化方法(1): 模拟队列

使用Bash脚本同时运行多个进程并无困难, 主要存在的问题是如何控制同时运行的进程数目. 上面的简单并行化方法使用时进程数无法控制, 因而功能有限, 因为大多数时候我们需要运行的作业数远远超过可用处理器数, 这种情况下若大量作业同时在后台运行, 会导致运行速度变慢, 并行效率大大下降. 一种简单的解决方案就是模拟一个限定最大进程数的队列, 以进程PID做为队列元素, 每隔一定时间检查队列, 若队列中有作业完成, 则添加新的作业到队列中. 这种方法还可以避免由于不同作业耗时不同而产生的无用等待. 下面是根据网上的代码改写的一种实现. 实用性更强的代码, 请参考原文.

# Language: bash
Njob=10 # 作业数目
Nproc=5 # 可同时运行的最大作业数 function CMD { # 测试命令, 随机等待几秒钟
n=$((RANDOM % 5 + 1))
echo "Job $1 Ijob $2 sleeping for $n seconds ..."
sleep $n
echo "Job $1 Ijob $2 exiting ..."
}
function PushQue { # 将PID压入队列
Que="$Que $1"
Nrun=$(($Nrun+1))
}
function GenQue { # 更新队列
OldQue=$Que
Que=""; Nrun=0
for PID in $OldQue; do
if [[ -d /proc/$PID ]]; then
PushQue $PID
fi
done
}
function ChkQue { # 检查队列
OldQue=$Que
for PID in $OldQue; do
if [[ ! -d /proc/$PID ]] ; then
GenQue; break
fi
done
} for((i=1; i<=$Njob; i++)); do
CMD $i &
PID=$!
PushQue $PID
while [[ $Nrun -ge $Nproc ]]; do
ChkQue
sleep 1
done
done
wait
一个更简洁的方法是记录PID到数组, 通过检查PID存在与否以确定作业是否运行完毕. 可实现如下
# Language: bash
Njob=10 # 作业数目
Nproc=5 # 可同时运行的最大作业数 function CMD { # 测试命令, 随机等待几秒钟
n=$((RANDOM % 5 + 1))
echo "Job $1 Ijob $2 sleeping for $n seconds ..."
sleep $n
echo "Job $1 Ijob $2 exiting ..."
} PID=() # 记录PID到数组, 检查PID是否存在以确定是否运行完毕
for((i=1; i<=Njob; )); do
for((Ijob=0; Ijob<Nproc; Ijob++)); do
if [[ $i -gt $Njob ]]; then
break;
fi
if [[ ! "${PID[Ijob]}" ]] || ! kill -0 ${PID[Ijob]} 2> /dev/null; then
CMD $i $Ijob &
PID[Ijob]=$!
i=$((i+1))
fi
done
sleep 1
done
wait
3. 进程数可控的并行化方法(2): 命名管道

上面的并行化方法也可利用命名管道来实现, 命名管道是Linux下进程间进行通讯的一种方法, 也称为先入先出(fifo, first in first out)文件. 具体方法是创建一个fifo文件, 作为进程池, 里面存放一定数目的”令牌”. 作业运行规则如下: 所有作业排队依次领取令牌; 每个作业运行前从进程池中领取一块令牌, 完成后再归还令牌; 当进程池中没有令牌时, 要运行的作业只能等待. 这样就能保证同时运行的作业数等于令牌数. 前面的模拟队列方法实际就是以PID作为令牌的实现.

据我已查看的资料, 这种方法在网络上讨论最多. 实现也很简洁, 但理解其代码需要的Linux知识较多. 下面是我改写的示例代码及其注释.

# Language: bash
Njob=10 # 作业数目
Nproc=5 # 可同时运行的最大作业数 function CMD { # 测试命令, 随机等待几秒钟
n=$((RANDOM % 5 + 1))
echo "Job $1 Ijob $2 sleeping for $n seconds ..."
sleep $n
echo "Job $1 Ijob $2 exiting ..."
} Pfifo="/tmp/$$.fifo" # 以PID为名, 防止创建命名管道时与已有文件重名,从而失败
mkfifo $Pfifo # 创建命名管道
exec 6<>$Pfifo # 以读写方式打开命名管道, 文件标识符fd为6
# fd可取除0, 1, 2,5外0-9中的任意数字
rm -f $Pfifo # 删除文件, 也可不删除, 不影响后面操作 # 在fd6中放置$Nproc个空行作为令牌
for((i=1; i<=$Nproc; i++)); do
echo
done >&6 for((i=1; i<=$Njob; i++)); do # 依次提交作业
read -u6 # 领取令牌, 即从fd6中读取行, 每次一行
# 对管道,读一行便少一行,每次只能读取一行
# 所有行读取完毕, 执行挂起, 直到管道再次有可读行
# 因此实现了进程数量控制
{ # 要批量执行的命令放在大括号内, 后台运行
CMD $i && { # 可使用判断子进程成功与否的语句
echo "Job $i finished"
} || {
echo "Job $i error"
}
sleep 1 # 暂停1秒,可根据需要适当延长,
# 关键点,给系统缓冲时间,达到限制并行进程数量的作用
echo >&6 # 归还令牌, 即进程结束后,再写入一行,使挂起的循环继续执行
} & done wait # 等待所有的后台子进程结束
exec 6>&- # 删除文件标识符

注意:

(1) exec 6<>$Pfifo 这一句很重要, 若无此语句, 向$Pfifo写入数据时, 程序会被阻塞, 直到有read读出了文件中的数据为止. 而执行了此语句, 就可以在程序运行期间不断向文件写入数据而不会阻塞, 并且数据会被保存下来以供read读出.

(2) 当$Pfifo中已经没有数据时, read无法读到数据, 进程会被阻塞在read操作上, 直到有子进程运行结束, 向$Pfifo写入一行.

(3) 核心执行部分也可使用如下方式

# Language: bash
for((i=1; i<=$Njob; i++)); do
read -u6
(CMD $i; sleep 1; echo >&6) &
done

{}和()的区别在shell是否会衍生子进程

(4) 此方法在目前的Cygwin(版本1.7.27)下无法使用, 因其不支持双向命名管道. 有人提到一个解决方案, 使用两个文件描述符来替代单个文件描述符, 但此方法我没有测试成功.

参考资料

  1. 简洁的模拟队列方法实现

    shell里如何实现”多线程”?

  2. 模拟队列方法, 实用性更强的代码

    A srcipt for running processes in parallel in Bash

  3. 命名管道方法及其解释
  4. Cygwin下双向命名管道的问题: bi-directional named pipe

原文:http://jerkwin.github.io/2013/12/14/Bash脚本实现批量作业并行化/

【转】Bash脚本实现批量作业并行化的更多相关文章

  1. Bash脚本实现批量作业并行化

    http://jerkwin.github.io/2013/12/14/Bash%E8%84%9A%E6%9C%AC%E5%AE%9E%E7%8E%B0%E6%89%B9%E9%87%8F%E4%BD ...

  2. (1)使用bash脚本实现批量添加用户

    脚本实现内容: 可以指定用户名前缀,指定添加数量的批量添加用户脚本,密码为10为随机小写字母,并把用户名和密码写入文件中. 脚本代码: #!/bin/bashread -p "用户名前缀:& ...

  3. 生产力工具:shell 与 Bash 脚本

    生产力工具:shell 与 Bash 脚本 作者:吴甜甜 个人博客网站: wutiantian.github.io 注意:本文只是我个人总结的学习笔记,不适合0基础人士观看. 参考内容: 王顶老师 l ...

  4. (转)通过shell脚本实现批量添加用户和设置随机密码以及生产环境如何批量添加

    通过shell脚本实现批量添加用户和设置随机密码以及生产环境如何批量添加 原文:http://www.21yunwei.com/archives/4773 有一个朋友问我如何批量创建用户和设置密码 , ...

  5. shell脚本:批量修改文件名

    参考链接1:shell脚本:批量修改文件名(删除文件名中字符) 参考链接2:linux shell 字符串操作详解 (长度,读取,替换,截取,连接,对比,删除,位置 ) 参考链接3:每天一个linux ...

  6. SqlServer批量压缩数据库日志-多数据库批量作业,批量备份还原

    原文:SqlServer批量压缩数据库日志-多数据库批量作业,批量备份还原 --作业定时压缩脚本 多库批量操作 DECLARE @DatabaseName NVARCHAR(50) DECLARE @ ...

  7. 【shell脚本】批量修改扩展名===modifyExtension.sh

    前提:需切换到需要批量修改扩展名的目录下,运行脚本 [root@VM_0_10_centos shellScript]# cat modifyExtension.sh #!/bin/bash # 编写 ...

  8. Bash脚本编程学习笔记08:函数

    官方资料:Shell Functions (Bash Reference Manual) 简介 正如我们在<Bash脚本编程学习笔记06:条件结构体>中最后所说的,我们应该把一些可能反复执 ...

  9. Bash脚本编程学习笔记07:循环结构体

    本篇中涉及到算术运算,使用了$[]这种我未在官方手册中见到的用法,但是确实可用的,在此前的博文<Bash脚本编程学习笔记03:算术运算>中我有说明不要使用,不过自己忘记了.大家还是尽量使用 ...

随机推荐

  1. no ocijdbc11 in java.library.path linux

    no ocijdbc11 in java.library.path linux vi /etc/profile export ORACLE_HOME=/oracle/database/oracle/p ...

  2. JSP--监听HTTP会话

    来源: <http://www.cnblogs.com/eflylab/archive/2007/01/16/621953.html> ServletListener 之 监听HTTP会话 ...

  3. Redis系统管理相关指令简介

    常用命令列表 DBSIZE                                            返回当前数据库 Key 的数量 INFO                       ...

  4. 14款经典的MySQL客户端软件

    1. EMS MySQL Manager 强大的mysql管理工具,允许用户通过图形界面创建或编辑数据库对象,并提供通过sql语句管理用户和权限,通过图形界面建立sql语句,自动生成html格式的数据 ...

  5. Java-Thread

    1. 线程的创建和启动 1.1 继承Thread 在run方法里,通过this获取当前线程. 多个线程不能共享实例变量. 1.2 通过实现接口 1.2.1 实现Runable接口 在run方法里,只能 ...

  6. IIS SMTP status codes

    Here are the meaning of SMTP status codes. Status Code Description 211 System status, or system help ...

  7. android打印调用栈

    在某些机器上,不能下断点,出现了某个诡异的问题,想到唯一的解决方式,就是打印调用栈了,google发现这个,记录下,以后备用 Log.d(",Log.getStackTraceString( ...

  8. 进程间通信IPC之--共享内存

    每个进程各自有不同的用户地址空间,任何一个进 程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲 区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲 ...

  9. Jedis 连接redis超时

    redis默认不允许远程连接 用vi打开Redis服务器的配置文件redis.conf ~ sudo vi /etc/redis/redis.conf #注释bind #bind 127.0.0.1 ...

  10. javaweb工程,Servlet里面获取当前WEB跟路径的文件绝对路径地址

    String base_Url = request.getSession().getServletContext().getRealPath("/"); String window ...