#!/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中设置的位。html
为了比较并行和串行的区别,咱们先写个串行的脚本:linux
#!/bin/bash start=`date "+%s"` for i in {1..10} do echo $i; sleep 2 done end=`date "+%s"` echo "Time: `expr $end - $start`"
执行结果以下:shell
$ sh series.sh 1 2 3 4 5 6 7 8 9 10 Time: 21
从结果来开,执行完上面10次任务,每次任务2秒,总耗时21秒,符合代码的逻辑。bash
先将上面的串行任务改为多线程并行任务。微信
#!/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万个进程吧。为了控制进程,咱们引入了管道
和文件操做符
。网站
管道就像水管,有流入才会有流出,水管数水流的通道,管道是数据的通道。管道分为无名管道和有名管道。url
管道类别 | 命令 | 栗子 | ||
---|---|---|---|---|
无名管道 | 经常使用的` | `就是管道,只不过是无名的,能够直接做为两个进程的数据通道 | `echo "hello world, I'm a test" | grep "test"` |
有名管道 | mkfilo 能够建立一个管道文件 |
mkfiflo testfifo |
管道有一个特色,若是管道中没有数据,那么取管道数据的操做就会阻塞,直到管道内进入数据,而后读出后才会终止这一操做,同理,写入管道的操做若是没有读取操做,这一个动做也会阻塞。spa
当经过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行:
exec 1000<>testfifo
来实现,但关闭时必须分开来写。>
读的绑定,<
标识写的绑定 <>
则标识对文件描述符1000的全部操做,其等同于对管道文件testfifo的操做。第6-8行:
建立管道文件
,文件操做符绑定
,删除管道文件
第12-15行:
行
为单位的。第32-61行:
$total
个任务($total是变量,是读取的urls的总行数,值大于0),咱们须要保证后台只有10个进程在同步运行(固然这段代码有点小遗憾,就是未能根据总行数决定用多少个进程,加入总行数小于10,但咱们建立了10行空字符串,但这并不影响咱们的测试) 。read -u1000
的做用是:读取一次管道中的一行,在这儿就是读取一个空行。read -u1000
这儿始终停顿。you-get
下载url中的图片、语音,若是下载失败,最多尝试5次。关于you-get
参考这篇文章《You-Get:支持 80 多个网站的命令行多媒体下载器》了解其更多。第64-69行:
exec 1000>&-
和exec 1000<&-
是关闭 fd1000
。该文首发《虚怀若谷》我的博客,转载前请务必署名,转载请标明出处。
古之善为道者,微妙玄通,深不可识。夫惟不可识,故强为之容:豫兮若冬涉川,犹兮若畏四邻,俨兮其若客,涣兮若冰之释,敦兮其若朴,旷兮其若谷,混兮其若浊。
孰能浊以静之徐清?孰能安以动之徐生?
保此道不欲盈。夫惟不盈,故能敝而新成。
请关注个人微信公众号:下雨就像弹钢琴,Thanks♪(・ω・)ノ