shell中实现多进程实际上就是将多个任务放到后台中执行而已,可是如今须要控制多进程并发的数量该如何实现呢?别急,咱们一步一步来实现这个目标,首先从最原始的串行执行开始:node
#!/bin/bash start=`date +%s` for i in $(seq 1 5); do echo test sleep 2 done end=`date +%s` time=$(($end - $start)) echo "time: $time"
执行结果:shell
# sh test1.sh test test test test test time: 10
这是最原始的一种处理方式,串行执行,没法有效利用计算机的资源,而且效率低下。若是能够一次执行多个任务的,把它放到后台便可,咱们作以下改进:bash
#!/bin/bash start=`date +%s` for i in $(seq 1 5); do { echo test sleep 2 }& done wait end=`date +%s` time=$(($end - $start)) echo "time: $time"
首先看下执行结果:并发
# sh test2.sh test test test test test time: 2
说下改动,首先我把for循环中的代码用{}包为一个块,而后增长&符号使其后台运行,以后增长wait指令,该指令会等待全部后台进程结束,若是不加wait,程序直接往下走,最终打出的time将会是0。如今程序已经由以前的10秒缩短为2秒,彷佛效果不错,不过试想这样一个场景,有1000个这样的任务,若是仍是以这种方式执行,机器负载是扛不住的,咱们必须想一种办法来控制进程的并发数,那就是管道和文件描述符。
首先介绍下管道(pipe):ide
管道有一个显著的特色,若是管道里没有数据,那么去取管道数据时,程序会阻塞住,直到管道内进入数据,而后读取才会终止这个操做,反之,管道在执行写入操做时,若是没有读取操做,一样会阻塞,下面是实例:函数
# mkfifo pipefile # echo test > pipefile # 此时会阻塞在这
此时我新开一个窗口再执行读操做code
# cat pipefile test
这时窗口一中的进程才会终止,再来看先读的状况:进程
# cat pipefile # 一样阻塞停滞在此
新开窗口执行写操做:ip
# echo test > pipefile
这时窗口一会马上读出内容并顺利完成
接下来看一下文件描述符
Linux系统在初始运行时,会自动绑定三个文件描述符0 1 2 对应 stdin ,stdout, stderr,在/proc/self/fd能够找到资源
# cd /proc/self/fd # ll total 0 lrwx------. 1 root root 64 Mar 27 03:22 0 -> /dev/pts/0 lrwx------. 1 root root 64 Mar 27 03:22 1 -> /dev/pts/0 lrwx------. 1 root root 64 Mar 27 03:22 2 -> /dev/pts/0 lrwx------. 1 root root 64 Mar 27 03:23 255 -> /dev/pts/0 # 绑定到咱们的终端设备上 # echo test > /proc/self/fd/1 test # echo test > /proc/self/fd/2 test
输出到几个文件的内容会打印到控制台上
咱们可使用exec 指令自行定义、绑定文件描述符,文件描述符的取值范围是3-(ulimit -n)-1
在我本机这个范围是3-1023
# ulimit -n 1024 # exec 1024<>pipefile -bash: 1024: Bad file descriptor # exec 1000<>pipefile #
下面开始介绍如何使用管道文件和文件描述符来控制进程并发:
mkfifo tm1 exec 5<>tm1 rm -f tm1
首先建立一个管道文件tm1,而后使用exec命令将该文件的输入输出绑定到5号文件描述符,然后删除该管道文件,这里删除的只是该文件的Inode,实际文件已由内核open函数打开,这里删除并不会影响程序执行,当程序执行完后,文件描述符会被系统自动回收。
for ((i=1;i<=10;i++)); do echo >&5 done
经过一个for循环向该文件描述符写入10个空行,这个10其实就是咱们定义的后台进程数量,这里须要注意的是,管道文件的读取是以行为单位的。
for ((j=1;j<=100;j++)); do read -u5 { echo test$j sleep 2 echo >&5 }& done wait
这里假定我后台有100个任务,而咱们在后台保证只有10个进程在同时运行
read -u5 的做用是,读取5号文件描述符中的一行,就是读取一个空行
在减小文件描述符的一个空行以后,在后台执行一次任务,而任务在执行完成之后,会向文件描述符中再写入一个空行,这是为何呢,由于若是咱们写入空行的话,当后台放入了10个任务以后,因为没有可读取的空行,read -u5就会被阻塞住!
exec 5>&- exec 5<&-
咱们生成作绑定时 能够用 exec 5<>tm1 来实现,但关闭时必须分开来写> 读的绑定,< 标识写的绑定 <> 则标识 对文件描述符5的全部操做等同于对管道文件tm1的操做。
完整代码以下:
#!/bin/bash mkfifo tm1 exec 5<>tm1 rm -f tm1 for ((i=1;i<=10;i++)); do echo >&5 done for ((j=1;j<=100;j++)); do read -u5 { echo test$j sleep 2 echo >&5 }& done wait exec 5>&- exec 5<&-
这样,就实现了进程的并发控制