案例1 通过文件描述符控制并发数

#用fifo处理shell下多进程并发:
#!/bin/bash
#author : vaedit
#date : 2017-8-15
#需要执行的工作函数
function mima(){
  openssl rand -base64 40 >> ssf.txt
}
#判断是否存在fifo文件
if  [ -e /tmp/$$.fifo ];then
  rm -rf /tmp/$$.fifo
fi
#创建$$.fifo 主要是为了防止同名
fifo_file=/tmp/$$.fifo
mkfifo $fifo_file
#把文件表述符fd6和fifo文件绑定
exec 6<>"$fifo_file"
process_num=10
#向fd6中输入10个回车
for ((i=0;i<$process_num;i++))
do
  echo 
done >&6
#处理业务
for ((a=0;a<10;a++))  #这里写10表示业务总条数,比如业务总共要执行100次,就填100
do
  read -u6 ##read -u6命令执行一次,相当于尝试从fd6中获取一行,如果获取不到,则阻塞#获取到了一行后,fd6就少了一行了,开始处理子进程,子进程放在后台执行
  {
  mima
  sleep 3  # 暂停3秒,这里是关键点,其实引入管道模拟多线程的关键就是为了这个暂停的3秒(实际上是微大于3秒的),让系统有个缓冲的时间,起到限制所谓并发的进程数量。
  echo >&6  #表示向fd6中输入一行,补充刚才消耗的
  } &
done
wait #等待所有的后台子进程结束
#关闭fd6
exec 6>&-

案例2 通过xargs命令实现多线程并发

xargs命令解析
-0:如果输入的stdin含有特殊字符,例如反引号`、反斜杠\、空格等字符时,xargs可以将它还原成一般字符。为xargs的默认选项。
-e <flag>,-E <flag>,--eof=<eof-str>:eof是end of file string的意思。flag可以是一个字符串或者是由空格分隔的多个字符串,当xargs分析到这个flag时,就会停止工作。
-p:当每次执行一个argument的时候询问一次用户。
-n <num>:表示命令在执行的时候一次使用的argument的个数,由num指定,默认是用所有的参数。
-t:表示先打印命令,然后再执行。
-a <file>:从文件中读入作为sdtin。
-i,-I:其中-I某些Linux版本不支持。将xargs的输出每一项参数,单独赋值给后面的命令,参数需要用{}代替。
-r:或者--no-run-if-empty,当xargs的输入为空的时候则停止xargs,不用再去执行后面的命令了,-r是xargs的默认选项。
-s <num>:命令行的最大字符数,指的是xargs后面那个命令的最大命令行字符数,包括命令、空格和换行符。每个参数单独传入xargs后面的命令。
-L <line_num>:设置标准输入中最大的行数作为命令每一次执行的参数。
-d <delim>, --delimiter=<delim>: xargs处理标准输入默认是按换行符和空格作为分隔符,输出arguments的分隔符是空格,这里修改xargs处理标准输入时的分隔符。
-x:eXit的意思,主要是配合-s使用,当命令行字符数大于-s指定的数值时,退出xargs。
-P:修改最大的进程数,默认是1,为0时候为as many as it can。
实例1
#/bin/bash
all_num=10
thread_num=5
a=$(date +%H%M%S)
seq 1 ${all_num} | xargs -n 1 -I {} -P ${thread_num} sh -c "sleep 1;echo {}"
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"
实例2
#通过seq 1 20 组合出一组以换行或者空格组成的数据,从而来控制总共需要执行后面脚本的次数,工作中使用,可以预先把需要的数据格式处理好,如
vim name.txt
ssf 11
vaedit 12
zhangsan 13
#然后通过-n 来控制传递给sh test.sh 的参数个数,如上test.sh 执行一次需要用到2个参数,名字和年龄就需要指定-n 2, 示例:cat name.txt | xargs -n 2 -P 10 sh test.sh

seq 1 20 |xargs -n 1 -P 10 sh test.sh

cat test.sh
#!/bin/bash
echo $1
sleep 2
实例3
#该用法只适合只有一个参数需要传递给test.sh的情况下
seq 1 20 |xargs -i -P 10 sh test.sh {}
seq 1 20 |xargs -I {} -P 10 sh test.sh {} #和上面意思一样,只是-i和-I的用法区别

cat test.sh
#!/bin/bash
echo $1
sleep 2

案例3 使用GNU parallel命令控制并发数

#安装
yum install epel-release -y
yum install parallel -y
echo -e "will cite\n" | parallel --bibtex

parallel -j 5 "sleep 1;echo {}" ::: `seq 1 10`

#示例1
#用法和xargs很像
find . -name "*jpeg" | parallel -I% --max-args 1 convert % %.png

这是两条命令的组合:find 命令,用于收集需要操作的对象;parallel 命令,用于对象排序并确保每个对象按需处理。
find . -name "*jpeg" 查找当前目录下以 jpeg 结尾的所有文件。
parallel 调用 GNU Parallel。
-I% 创建了一个占位符 %,代表 find 传递给 Parallel 的内容。如果不使用占位符,你需要对 find 命令的每一个结果手动编写一个命令,而这恰恰是你想要避免的。
--max-args 1 给出 Parallel 从队列获取新对象的速率限制。考虑到 Parallel 运行的命令只需要一个文件输入,这里将速率限制设置为 1。假如你需要执行更复杂的命令,需要两个文件输入(例如 cat 001.txt 002.txt > new.txt),你需要将速率限制设置为 2。
convert % %.png 是你希望 Parallel 执行的命令。
管道输出的每一行对应 parallel 的一个参数,所有参数构成参数行

#示例2
ls -1 | parallel --max-args=2 cat {1} {2} ">" {1}_{2}.person
#把当前目录下的第一个和第二个文件中得内容合并写入到新的文件中,其中{1},{2} ,代表管道传入的第一个参数和第二个参数,参数已行分割

#这里介绍下{}用法
{.} 表示去掉文件.后缀
{\} 表示去掉文件目录相当于basename
{\\} 去掉第一层目录后,剩下的目录
{\.} 表示去掉文件目录后,把文件名的.后缀去掉
{#} 表示并发进程的序列号

#常用参数
--null 会把管道传入的每一行的参数通过空格合并到一行变成一个参数
::: 后面表示要传入的参数,可以用这个替代管道符传入参数,还是按行分割
-L 每次读取的参数行数
#示例3
#首先,需要创建一个文本文件,每行包含一个命令:
cat jobs2run
bzip2 oldstuff.tar
oggenc music.flac
opusenc ambiance.wav
convert bigfile.tiff small.jpeg
ffmepg -i foo.avi -v:b 12000k foo.mp4
xsltproc --output build/tmp.fo style/dm.xsl src/tmp.xml
bzip2 archive.tar

#执行
parallel --jobs 6 < jobs2run 
#现在文件中对应的全部任务都在被 Parallel 执行。如果任务数量超过允许的数目(LCTT 译注:应该是 --jobs/-j 指定的数目或默认值,也就是并发数,如过不指定-j,就尽可能使用和cpu一样的并发数),Parallel 会创建并维护一个队列,直到任务全部完成。

#示例4
#现实使用中可以把参数按照行的形式先写入文本中如name.txt 然后在脚本中调用
cat name.txt
vaedit 11
vaedit1 22
vaedit2 33
vaedit3 44

cat name.txt |parallel -j 2 --max-args=1 sh test.sh {1} #这里parallel还是把每行参数当成一个参数来执行,所以如果我们要识别名字(vaedit)和 年龄(11),可以在test.sh中处理

cat test.sh
#!/bin/bash
a=$1
a1=`echo $a|awk '{print $1}'`
a2=`echo $a|awk '{print $2}'`
echo "名字是 $a1"
echo "年龄是 $a2"
sleep 1


#案例4
cat bigfile.txt | parallel --block 10M --pipe grep 'pattern' #pipe意思是直接把输入当成后面命令的标准输入而不是参数
# --block 每个job,进程只读取的文件大小
# -L 每次读取的行数
#-N 每个job,也就是进程只读取多少行

cat num1000000 | parallel --pipe --block 2M wc -l
cat bigfile.txt | parallel  --pipe wc -l | awk '{s+=$1} END {print s}'