主要记录一下Shell脚本中的命令的并发和串行执行。
默认的情况下,Shell脚本中的命令是串行执行的,必须等到前一条命令执行完后才执行接下来的命令,但是如果我有一大批的的命令需要执行,而且互相又没有影响的情况下(有影响的话就比较复杂了),那么就要使用命令的并发执行了。
看下面的代码:
#!/bin/bash for(( i = 0; i < ${count}; i++ )) do commands1 done commands2
修改后的代码如下:
#!/bin/bash for(( i = 0; i < ${count}; i++ )) do { commands1 }& done commands2
这样的话commands1就可以并行执行了。 实质是将commands1作为后台进程在执行,这样主进程就不用等待前面的命令执行完毕之后才开始执行接下来的命令。
但是我的本来目的是让commands1的这个循环都执行结束后,再用command2去处理前面的结果。如果像上面这样写的话,在commands1都还没结束时就已经开始执行commands2了,得到了错误的结果。
再次修改代码如下:copy
#!/bin/bash for(( i = 0; i < ${count}; i++ )) do { commands1 }& done wait commands2
上面这样就可以达到预期的目的了,先是所有的commands1在后台并行执行,等到循环里面的命令都结束之后才执行接下来的commands2。
对于上面的代码,如果count值特别大的时候,我们应该控制并发进程的个数,不然会影响系统其他进程的运行,甚至死机。
下面的代码控制并发个数。其实是利用令牌原理实现,一个线程要运行,首先要拿到令牌在该代码中即read一行数据,读取不到数据就会暂停,否则就拿到数据就运行命令任务,完成后将令牌再放回,也即再在管道文件中写入一行数据,这里的数据是换行符, echo >&4 。这样另外的线程就可以再读该数据(拿到令牌 ),并运行。
#!/bin/bash tmpfile=$$.fifo #创建管道名称 mkfifo $tmpfile #创建管道 exec 4<>$tmpfile #创建文件标示4,以读写方式操作管道$tmpfile rm $tmpfile #将创建的管道文件清除 thred=4 #指定并发个数 seq=(1 2 3 4 5 6 7 8 9 21 22 23 24 25 31 32 33 34 35) #创建任务列表 # 为并发线程创建相应个数的占位 { for (( i = 1;i<=${thred};i++ )) do echo; #因为read命令一次读取一行,一个echo默认输出一个换行符,所以为每个线程输出一个占位换行 done } >&4 #将占位信息写入管道 for id in ${seq[*]} #从任务列表 seq 中按次序获取每一个任务 或者用: for id in ${seq} do read #读取一行,即fd4中的一个占位符 (./ur_command ${id};echo >&4 ) & #在后台执行任务ur_command 并将任务 ${id} 赋给当前任务ur_command;任务执行完后在fd4种写入一个占位符 ,&表示该部分命令/任务 并行处理 done <&4 #指定fd4为整个for的标准输入 wait #等待所有在此shell脚本中启动的后台任务完成 exec 4>&- #关闭管道
整个流程中read 和 echo 对fd4的交替写入和读取是并发处理的关键
可以想象 如果read 命令发现fd4中没有数据时 将等待fd4的数据
但是这有个缺点就是每个线程运行前都要拿到令牌,而且这个令牌的拿放是要消耗I/O的,有没有更好的方法实现?
可以用PV语句实现。也就是定义一个全局变量globleThread_num用来存放最大线程个数,还要有一个变量currentThread_num存放当前的线程个数,然后需要锁,当读取和改变currentThread_num的值的时候,需要锁住。
转载于:https://blog.51cto.com/leeyan/1858144