Shell多进程执行任务
展示代码
#!/bin/bash
trap "exec 1000>&-;exec 1000<&-;exit 0" 2
# 分别为 创建管道文件,文件操作符绑定,删除管道文件
mkfifo testfifo
exec 1000<>testfifo
rm -rf testfifo
# 对文件操作符进行写入操作。
# 通过一个for循环写入10个空行,这个10就是我们要定义的后台线程数量。
for ((n=1; n<=10; n++))
do
echo >&1000
done
# 创建一个备份目录
if [[ ! -d back ]]; then
mkdir back
fi
# 开始时间记录
start=`date "+%s"`
# 获取URL总数,如果总数为0,直接退出
total=`cat urls | wc -l`
if [[ $total == 0 ]]; then
echo 'urls总数为空'
exit 0
fi
# 遍历URLS文件,开始执行下载
for ((i=1;i<=$total;i++))
do {
# 从testfifo中读取一行
read -u1000
{
# 增加尝试次数,最大5次
for j in {1..5}
do
# 判断单独进程文件目录是否存在,不存在则创建目录
download_dir=audio"$i"
if [[ ! -d $download_dir ]]; then
mkdir -p $download_dir
fi
echo "往目录${download_dir}中开始下载文件,尝试次数:${j}"
# 读取URLS中的一行,下载文件
you-get -o $download_dir `sed -n "$i"p urls | tr -d '\r'`
# 校验是否有异常,如果没有异常,则跳出循环,执行外下一条,如果有异常,再次尝试下载
if [[ $? != 0 ]]; then
mv $download_dir/* back
rm -rf $download_dir
else
break
fi
done
# 向文件操作符中写入一个空行
echo >&1000
}&
}
done
# 等待所有任务完成
wait
end=`date "+%s"`
echo "time: `expr $end - $start`"
exec 1000>&-
exec 1000<&-
所谓多进程,就是将一个任务划分成多个子任务放在后台执行。"FIFO"是一种特殊的文件类型,它允许独立的进程通讯. 一个进程打开FIFO文件进行写操作,而另一个进程对之进行读操作, 然后数据便可以如同在shell或者其它地方常见的的匿名管道一样流线执行。默认情况下,创建的FIFO的模式为0666('a+rw')减去umask中设置的位。
串行、并行
串行任务
为了比较并行和串行的区别,我们先写个串行的脚本:
#!/bin/bash
start=`date "+%s"`
for i in {1..10}
do
echo $i;
sleep 2
done
end=`date "+%s"`
echo "Time: `expr $end - $start`"
执行结果如下:
$ sh series.sh
1
2
3
4
5
6
7
8
9
10
Time: 21
从结果来开,执行完上面10次任务,每次任务2秒,总耗时21秒,符合代码的逻辑。
并行任务
先将上面的串行任务改成多线程并行任务。
#!/bin/bash
start=`date "+%s"`
for i in {1..10}
do
{
echo $i;
sleep 2
}&
done
wait
end=`date "+%s"`
echo "Time: `expr $end - $start`"
执行上面脚本的结果如下:
$ sh paralle.sh
1
2
3
4
5
6
7
8
9
10
Time: 2
通常,用{}
将不占处理器却很耗时的任务放包装一个块,通过&
放置在后台运行,以达到节约时间的效果。上面并行代码,我们把10次任务全部放在后台执行,每个人物耗时2秒,由于并行执行,总耗时也就是Max(任务耗时)=2秒。
{
echo $i;
sleep 2
}&
在小任务跟前,这种方式运用起来得心应手,但是在任务量过大的时候,这种方式的缺点也就显而易见了:无法控制运行在后台的进程数,不能就10万个任务就是跑10万个进程吧。为了控制进程,我们引入了管道
和文件操作符
。
管道、文件操作符
管道
管道就像水管,有流入才会有流出,水管数水流的通道,管道是数据的通道。管道分为无名管道和有名管道。
管道类别 | 命令 | 栗子 |
---|---|---|
无名管道 | 常用的| 就是管道,只不过是无名的,可以直接作为两个进程的数据通道 |
echo "hello world, I'm a test" | grep "test" |
有名管道 | mkfilo 可以创建一个管道文件 |
mkfiflo testfifo |
管道有一个特点,如果管道中没有数据,那么取管道数据的操作就会阻塞,直到管道内进入数据,然后读出后才会终止这一操作,同理,写入管道的操作如果没有读取操作,这一个动作也会阻塞。
当通过echo命令往fifotest管道中写入数据时,由于没有任何其他消费进程对管道操作,所以,该管道阻塞,直到再打开一个窗口且通过cat操作该管道。
同理,先操作读取管道也会出现阻塞的情况。
通过以上实验,看以看到,仅仅一个管道文件似乎很难实现控制后台线程数,因此我们接下来简单介绍 文件操作符
。
文件操作符
系统运行起始,就相应设备自动绑定到了 三个文件操作符 分别为0
、1
、2
对应 stdin
、stdout
、 stderr
。在 /proc/self/fd
或者/dev/fd
中可以看到这三个对应文件:
输出到这三个文件的内容都会显示出来。只是因为显示器作为最常用的输出设备而被绑定。
在Linux中,可以通过exec
指令自行定义、绑定文件操作符,文件操作符一般从3~(n-1)都可以随便使用,此处的n为ulimit -n
的定义值。
从上图可以看出本机的n
值为8192
,所以文件操作符只能使用0-8192
,可自行定义的就只能是3-8192
。
虽然exec和source都是在父进程中直接执行,但exec这个与source有很大的区别,source是执行shell脚本,而且执行后会返回以前的shell。而exec的执行不会返回以前的shell了,而是直接把以前登陆shell作为一个程序看待,在其上经行复制。
exec可参考此文:《linux 下的 mkfifo、exec 命令使用》
代码分析
第3行:
- 接受信号 2 (ctrl +C)做的操作。
- 我们生成文件描述符并做绑定时,可以用
exec 1000<>testfifo
来实现,但关闭时必须分开来写。 >
读的绑定,<
标识写的绑定<>
则标识对文件描述符1000的所有操作,其等同于对管道文件testfifo的操作。
第6-8行:
- 分别为
创建管道文件
,文件操作符绑定
,删除管道文件
- 可能会有疑问,为什么不能直接使用管道文件呢?事实上,这并非多此一举,刚才已经说明了管道文件的一个重要特性了,那就是读写必须同时存在,缺少某一种操作,另一种操作就是阻塞,而绑定文件操作符正好解决了这个问题。
第12-15行:
- 对文件操作符进行写入操作。 通过一个 for 循环写入 10 个空行,这个 10 就是我们要定义的后台线程数量。
- 为什么写入空行而不是 10 个字符呢?这是因为,管道文件的读取是以
行
为单位的。 - 当我们试图用 read 读取管道中的一个字符时,结果是不成功的,上面的例子已经证实了使用cat是可以读取的。
第32-61行:
- 遍历urls的总行数,循环处理url
- 25-29行是读取urls文件的总行数的逻辑(看开篇代码)。
- 这里我们有
$total
个任务($total是变量,是读取的urls的总行数,值大于0),我们需要保证后台只有10个进程在同步运行(当然这段代码有点小遗憾,就是未能根据总行数决定用多少个进程,加入总行数小于10,但我们创建了10行空字符串,但这并不影响我们的测试) 。 read -u1000
的作用是:读取一次管道中的一行,在这儿就是读取一个空行。- 减少操作附中的一个空行之后,执行一次任务(当然是放到后台执行),需要注意的是,这个任务在后台执行结束以后会向文件操作符中写入一个空行,这就是重点所在,如果我们不在某种情况某种时刻向操作符中写入空行,那么结果就是:在后台放入10个任务之后,由于操作符中没有可读取的空行,导致
read -u1000
这儿始终停顿。 - 第38-56行,处理自己的业务,这里面是通过
you-get
下载url中的图片、语音,如果下载失败,最多尝试5次。关于you-get
参考这篇文章《You-Get:支持 80 多个网站的命令行多媒体下载器》了解其更多。
第64-69行:
- 等待所有进程执行结束。
exec 1000>&-
和exec 1000<&-
是关闭fd1000
。
Shell多进程执行任务的更多相关文章
- shell script 执行常用的两种方式
2016-11-17 直接输入脚本名执行 ./script #!/bin/bash# /root/shell/001 # 2016-11-17 test for script running name ...
- shell的执行顺序问题
&&,||,(),{},& 五个符号的运用 shell脚本执行命令的时候,有时候会依赖于前一个命令是否执行成功.而&&和||就是用来判断前一个命令执行效果的. ...
- shell各种执行方式区别
shell 脚本各种执行方式(source ./*.sh, . ./*.sh, ./*.sh)的区别 原文出处:http://blog.csdn.net/dance_rise/article/deta ...
- shell 后台执行命令
shell 后台执行命令方法: 1. nohup cmd & 后台会生成 nohup.out 文件 2.cmd >/路径/xx.log & 后台生成 xx. ...
- windows下建立文件的换行符^M导致linux下的shell脚本执行错误的解决方式
常常在windows下编辑的文件远程传送到linux下的时候每行末尾都会出现^M.这将导致shell脚本执行错误,主要是由于dos下的编辑器和linux下的编辑器对文件末行的回车符处理不一致导致. 主 ...
- shell多进程
之前需要多进程程序都是python实现,闲来无事弄了下shell多进程,发现so easy(笑哭) 代码上: #!/bin/bash sleep 10 & sleep 5& wait ...
- shell 多进程
shell 多进程来模拟多线程 (1){ } 建立代码块 (2)使用 & 将进程放入后台 [zheng@localhost ~]$ cat threads.sh #!/bin/bash ;i& ...
- 【Linux】 环境变量与shell配置&执行
■ 变量与环境变量 shell环境通常存在很多变量,变量可以通过echo $VAR或${VAR}的方式查看.set命令可以查看当前环境中的所有变量(包括一般的自定义变量和环境变量) 变量的设置通过简单 ...
- shell命令执行hive脚本(hive交互,hive的shell编程)
Hive执行方式 Hive的hql命令执行方式有三种: 1.CLI 方式直接执行 2.作为字符串通过shell调用hive –e执行(-S开启静默,去掉"OK","Tim ...
随机推荐
- 自定义JDBC工具类
因为数据库的连接代码都是固定的,为了将减少重复的代码的书写,可以将这些代码封装为一个工具类,获取数据库的连接对象. import java.sql.Connection; import java.sq ...
- charles 端口转发
本文参考:charles 端口转发 端口转发 端口转发(Port forwarding),有时被叫做隧道,是安全壳(SSH) 为网络安全通信使用的一种方法.端口转发是转发一个网络端口从一个网络节点到另 ...
- Java连载34-对象的内存分析、对象之间建立关系
一.内存分析 代码:引用可以是局部变量也可以是成员变量 public class Test1{ public static void main(String[] args){ User u = new ...
- pycharm最新版本激活码(永久有效) python安装教程
Mac 系统自带python 1.打开终端, 输入 python 可以查看python当前版本. 2.输入“python”回车后即进入解释器,例如打印“hello world!”, 可输入 ‘ pri ...
- linux环境下Nginx的配置及使用
切换到目录/usr/local/nginx/sbin,/usr/local为nginx的默认安装目录 #启动 ./nginx #查看命令帮助 ./nginx -h 验证配置文件状态 ./nginx - ...
- swagger2的简单使用
swagger2的简单使用 优点: 可以生成文档形式的API并提供给不同的团队使用 便于自己单测 无需过多冗余的word文档,这一点很重要,因为我在工作中就遇到这么一个情况,由于开发使用的文档和最新文 ...
- vue-router路由元信息及keep-alive组件级缓存
路由元信息?(黑人问号脸???)是不是这么官方的解释很多人都会一脸懵?那么我们说meta,是不是很多人恍然大悟,因为在项目中用到或者看到过呢? 是的,路由元信息就是我们定义路由时配置的meta字段:那 ...
- SPSS基础学习方差分析—单因素分析
为什么要进行方差分析? 单样本.两样本t检验其最终目的都是分析两组数据间是否存在显著性差异,但如果要分析多组数据间是否存在显著性差异就很困难,因此用方差分析解决这个问题:举例:t检验可以分析一个班男女 ...
- hadoop集群单点配置
=================== =============================== ----------------hadoop集群搭建 --------------------- ...
- Maven 梳理 - maven新建web项目提示"javax.servlet.http.HttpServlet" was not found on the Java Build Path
方法一: <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api&l ...