1 bash后台运行实现多进程
1.1 command & 后台运行
释放终端命令行,将command命令程序挂到后台继续执行,这样用户就不必等待该程序执行结束后才释放终端命令行,可以一边等待command后台执行一边做其他的事。但是,不可以关闭当前的终端,挂靠在后台的command命令是依赖于当前终端的,当关闭当前终端之后,后台的comman命令也会终止执行。
比如:使用sort指令对一个较大的文件进行排序
$ sort log > log.sorted &
1.2 nohup command & 后台运行
这种方式与之前的区别是,使用nohup来管理后台执行的command程序。这样可以是后台的command程序完全脱离终端,当终端关闭时,后台的command程序会在nohup管理下继续执行。如果你要运行一个非常耗时的程序时,建议使用nohup来管理后台程序。这是因为,当程序运行过程中,终端因为网速不稳定或者其他原因突然中断时,会导致运行中的程序中断。
使用方式:
$ nohup longtime.sh &
程序执行过程中的正常输出以及错误输出都被重定向到程序目录下的nohup.out文件中。也可以使用如下方式来指定重定向输出的文件:
$ nohup longtime.sh > myout 2>&1 &
1.3 后台任务实现多进程
只需将需要多进程执行的程序块全部使用command & 转移到后台执行即可。
#!/bin/bash
start=`date +"%s"`
for (( i=0; i<10; i++ ))
do
{
echo "multiprocess"
sleep 3
} & #将上述程序块放到后台执行
done
wait #等待上述程序结束
end=`date +"%s"`
echo "time: " `expr $end - $start`
使用上述方式,10次执行过程全部放到后台执行。这种方式虽然可以实现程序的多进程并发执行,但是我们无法控制运行在后台的进程数。为了实现进程数控制,需要使用管道和文件描述符。
2 使用fifo管道管理多进程
2.1 管道
管道可用于程序之间的数据共享,但是需要注意的是,这种数据共享是单向的。管道分两种:匿名管道和有名管道,这里仅讨论有名管道。
管道的特点:
1、需要有两个程序分别连接管道的两端,一个程序往管道里写数据,另一个程序从管道里取数据;
2、只有写数据的程序时,写入操作会一直处理阻塞状态,直到有另外的程序从管道中读数据;同理当只有读取的程序时,读取操作也会一直处于阻塞状态,直到有另外的程序往管道里写数据
3、当管道中没有数据时,取数据的程序会被阻塞,直到写数据的程序写数据到管道
有名管道被称为fifo有如下特点:
1、fifo拥有名称,并存在于文件系统之中
2、除非fifo两端同时存在读和写的程序,否则fifo的数据流将会阻塞
3、fifo由mkfifo这样的命令创建,而匿名管道是由shell自动创建
4、fifo是一个双向的字节流,数据流没有任何格式
2.2 使用fifo管理shell多进程
直接代码:
1 #!/bin/bash
2
3 if [ $# -ne 1 ]
4 then
5 echo Usage: split.sh 201610
6 exit 1
7 else
8 month=$1
9 fi
10 len=`echo $month | wc -L`
11 if [ $len != "6" ]
12 then
13 echo "err> The lenght of month is not valid ..."
14 fi
15
16
17 fp_out="/opt/log_combine/"
18 fp_tmp="/opt/log_combine/tmp/"
19 fp_in="/opt/tag_log/tag.log."
20
21 #获取所有日志文件的文件名
22 fns=`ls $fp_in$month*.gz`
23 if ls $fns >> log.split4month
24 then
25 echo "split the log begin!"
26 else
27 echo "err> can't find the file $fns"
28 exit 1
29 fi
30
31 trap "exec 1000>&-;exec 1000<&-;exit 0" 2
32
33 tmpfifo=$$.fifo
34 mkfifo $tmpfifo
35 exec 1000<>$tmpfifo
36 rm -fr $tmpfifo
37
38 thread_num=`grep "processor" /proc/cpuinfo | sort -u | wc -l`
39
40 for (( i=0; i<$thread_num; i++ ))
41 do
42 echo >&1000
43 done
44
45 for fn in $fns
46 do
47 fn_tmp=${fn##*/}
48 read -u1000
49 {
50 echo `date` $fn "begin!"
51 zcat $fn | gawk -F"\001" 'NF==6{
52 if($1~/\./){
53 suf = substr($1, index($1, ".")-1, 1)
54 }else{
55 suf = substr($1, length($1))
56 }
57
58 if(suf~/[0-9a-f]/){
59 print $1"\001"$2"\001"$4"\001"$6 >> "'$fp_tmp$fn_tmp'_" suf "_split"
60 #}else{
61 # print $0
62 }
63 }'
64 echo `date` $fn "end!"
65 echo >&1000
66 } &
67 done
68 echo "done !!!!!!"
- 31行:trap命令,程序接收Ctrl+c终端信号的处理
- exec 1000>&-:关闭文件描述符1000的写
- exec 1000<&-:关闭文件描述符1000的读
- 2:接收的是Ctrl+c信号
- 33-36行:
- 33行:获取当前程序的进程号
- 34行:mkfifo创建管道,管道名为当前进程的进程号
- 35行:exec命令,将文件描述符1000与上述管道进行绑定,其中<是读绑定,>是写绑定,这里可以使用<>将读写同时与管道进行绑定
- 36行:删除管道文件(不太明白为啥在这里就删除它)
- 特别说明:这里为何要将管道文件和文件描述符进行绑定,直接使用管道文件不可以吗?答案肯定是不可以,以下内容是我自己的理解。要实现多进程控制,就必须使用两个功能:第一,管道的特殊性质(阻塞功能);第二,通过对文件描述符的read和echo操作来控制管道中的数据内容(这才是控制多进程数量的关键)。为了满足上述功能,只能是将文件描述符和fifo绑定,使文件描述符同时具有文件读写功能以及管道功能
- 38行:获取当前系统的线程数N,以此为并行运行的进程数
- 40-42行:向文件描述符1000中写入N的空行,用来控制并行的进程数
- 45-67行:并行开启N的进程处理日志文件
- 48行:要从文件描述符1000中读取一行,如果当前文件描述符1000没有任何内容,read命令就会被阻塞(由管道特点决定)
- 49-66行:需要并行处理的程序块,其中66行最后的&表示将这个程序块放到后台执行
- 特别说明:最初管道中有N的空行,会有N个read命令顺利执行,后续的程序快也可以继续往下执行,但是后续的一个read命令因为管道中没有任何内容全部被阻塞,当执行中的某个程序块执行到65行时,会往管道中写入一个空行,于是阻塞队列中的read命令会从管道中读取到一个空行,后续程序块被放到后台得到执行。这就是通过fifo对并行程序进行控制的关键。