情景

shell脚本的执行效率虽高,但当任务量巨大时仍然需要较长的时间,尤其是需要执行一大批的命令时。因为默认情况下,shell脚本中的命令是串行执行的。如果这些命令相互之间是独立的,则可以使用“并发”的方式执行这些命令,这样可以更好地利用系统资源,提升运行效率,缩短脚本执行的时间。如果命令相互之间存在交互,则情况就复杂了,那么不建议使用shell脚本来完成多线程的实现。

为了方便阐述,使用一段测试代码。在这段代码中,通过seq命令输出1到10,使用for...in语句产生一个执行10次的循环。每一次循环都执行sleep 1,并echo出当前循环对应的数字。

注意:

  1. 真实的使用场景下,循环次数不一定等于10,或高或低,具体取决于实际的需求。
  2. 真实的使用场景下,循环体内执行的语句往往比较耗费系统资源,或比较耗时等。

请根据真实场景的各种情况理解本文想要表达的内容

$ cat test1.sh
#/bin/bash

all_num=10

a=$(date +%H%M%S)

for num in `seq 1 ${all_num}`
do
sleep 1
echo ${num}
done b=$(date +%H%M%S) echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

通过上述代码可知,为了体现执行的时间,将循环体开始前后的时间打印了出来。

运行结果:

$ sh test1.sh
1
2
3
4
5
6
7
8
9
10
startTime: 193649
endTime: 193659

10次循环,每次sleep 1秒,所以总执行时间10s。

方案

方案1:使用"&"使命令后台运行

在linux中,在命令的末尾加上&符号,则表示该命令将在后台执行,这样后面的命令不用等待前面的命令执行完就可以开始执行了。示例中的循环体内有多条命令,则可以以{}括起来,在大括号后面添加&符号。

$ cat test2.sh
#/bin/bash

all_num=10

a=$(date +%H%M%S)

for num in `seq 1 ${all_num}`
do
{
sleep 1
echo ${num}
} &
done b=$(date +%H%M%S) echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

运行结果:

sh test2.sh
startTime:	194147
endTime: 194147
[j-tester@merger142 ~/bin/multiple_process]$ 1
2
3
4
5
6
7
8
9
10

通过结果可知,程序没有先打印数字,而是直接输出了开始和结束时间,然后显示出了命令提示符[j-tester@merger142 ~/bin/multiple_process]$(出现命令提示符表示脚本已运行完毕),然后才是数字的输出。这是因为循环体内的命令全部进入后台,所以均在sleep了1秒以后输出了数字。开始和结束时间相同,即循环体的执行时间不到1秒钟,这是由于循环体在后台执行,没有占用脚本主进程的时间。

方案2:命令后台运行+wait命令

解决上面的问题,只需要在上述循环体的done语句后面加上wait命令,该命令等待当前脚本进程下的子进程结束,再运行后面的语句。

$ cat test3.sh
#/bin/bash

all_num=10

a=$(date +%H%M%S)

for num in `seq 1 ${all_num}`
do
{
sleep 1
echo ${num}
} &
done wait b=$(date +%H%M%S) echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

运行结果:

$ sh test3.sh
1
2
3
4
5
6
7
9
8
10
startTime: 194221
endTime: 194222

但这样依然存在一个问题:
因为&使得所有循环体内的命令全部进入后台运行,那么倘若循环的次数很多,会使操作系统在瞬间创建出所有的子进程,这会非常消耗系统的资源。如果循环体内的命令又很消耗系统资源,则结果可想而知。

最好的方法是并发的进程是可配置的。

方案3:使用文件描述符控制并发数

$ cat test4.sh
#/bin/bash

all_num=10
# 设置并发的进程数
thread_num=5 a=$(date +%H%M%S) # mkfifo 创建命名管道
tempfifo="my_temp_fifo"
mkfifo ${tempfifo} #关联fifo文件和fd6,使文件描述符为非阻塞式
exec 6<>${tempfifo}
rm -f ${tempfifo} # 为文件描述符创建占位信息
for ((i=1;i<=${thread_num};i++))
do
{
echo
}
done >&6 #
for num in `seq 1 ${all_num}`
do
{
read -u6 ##read -u6命令执行一次,相当于尝试从fd6中获取一行,如果获取不到,则阻塞获取到了
##一行后,fd6就少了一行了,开始处理子进程,子进程放在后台执行
{
sleep 1
echo ${num}
echo "" >&6 #完成后再补充一个空值到fd6中,释放一个锁
} &
}
done wait # 关闭fd6管道
exec 6>&- b=$(date +%H%M%S) echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

运行结果:

$ sh test4.sh
1
3
2
4
5
6
7
8
9
10
startTime: 195227
endTime: 195229

方案4:使用xargs -P控制并发数

xargs命令有一个-P参数,表示支持的最大进程数,默认为1。为0时表示尽可能地大,即方案2的效果。

$ cat test5.sh
#/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"

运行结果:

$ sh test5.sh 

1
2
3
4
5
6
8
7
9
10
startTime: 195257
endTime: 195259

方案5:使用GNU parallel命令控制并发数

GNU parallel命令是非常强大的并行计算命令,使用-j参数控制其并发数量。

$ cat test6.sh
#/bin/bash

all_num=10
thread_num=6 a=$(date +%H%M%S) parallel -j 5 "sleep 1;echo {}" ::: `seq 1 10` b=$(date +%H%M%S) echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

运行结果:

$ sh test6.sh
1
2
3
4
5
6
7
8
9
10
startTime: 195616
endTime: 195618

总结

“多线程”的好处不言而喻,虽然shell中并没有真正的多线程,但上述解决方案可以实现“多线程”的效果,重要的是,在实际编写脚本时应有这样的考虑和实现。
另外:
方案3、4、5虽然都可以控制并发数量,但方案3显然写起来太繁琐。
方案4和5都以非常简洁的形式完成了控制并发数的效果,但由于方案5的parallel命令非常强大,所以十分建议系统学习下。
方案3、4、5设置的并发数均为5,实际编写时可以将该值作为一个参数传入。

参考文章

  1. http://blog.csdn.net/qq_34409701/article/details/52488964
  2. https://www.codeword.xyz/2015/09/02/three-ways-to-script-processes-in-parallel/
  3. http://www.gnu.org/software/parallel/

相关知识点

  • wait命令
  • &后台运行
  • 文件描述符、mkfifo等
  • xargs命令
  • parallel命令

【Linux】shell脚本实现多并发的更多相关文章

  1. Shell脚本中的并发(转)

    转自http://blog.csdn.net/wangtaoking1/article/details/9838571 主要记录一下Shell脚本中的命令的并发和串行执行. 默认的情况下,Shell脚 ...

  2. Linux shell脚本编程(三)

    Linux shell脚本编程 流程控制: 循环语句:for,while,until while循环: while CONDITION; do 循环体 done 进入条件:当CONDITION为“真” ...

  3. Linux shell脚本编程(二)

    Linux shell脚本编程(二) 练习:求100以内所有偶数之和; 使用至少三种方法实现; 示例1: #!/bin/bash # declare -i sum=0 #声明一个变量求和,初始值为0 ...

  4. Linux shell脚本编程(一)

    Linux shell脚本编程: 守护进程,服务进程:启动?开机时自动启动: 交互式进程:shell应用程序 广义:GUI,CLI GUI: CLI: 词法分析:命令,选项,参数 内建命令: 外部命令 ...

  5. Linux Shell 脚本入门

    linux shell 脚本格式 #!/bin/sh#..... (注释)命令...命令... 使用vi 创建完成之后需设置权限 chmod +x filename.sh 执行命令: ./filena ...

  6. Linux Shell脚本入门--cut命令

    Linux Shell脚本入门--cut命令 cut cut 命令可以从一个文本文件或者文本流中提取文本列. cut语法 [root@www ~]# cut -d'分隔字符' -f fields &l ...

  7. Linux Shell脚本攻略 读书笔记

    Linux Shell脚本攻略 读书笔记 这是一本小书,总共253页,但内容却很丰富,书中的示例小巧而实用,对我这样总是在shell门前徘徊的人来说真是如获至宝:最有价值的当属文本处理,对这块我单独整 ...

  8. 阿里Linux Shell脚本面试25个经典问答

    转载: 阿里Linux Shell脚本面试25个经典问答 Q:1 Shell脚本是什么.它是必需的吗? 答:一个Shell脚本是一个文本文件,包含一个或多个命令.作为系统管理员,我们经常需要使用多个命 ...

  9. Linux Shell脚本教程

    v\:* {behavior:url(#default#VML);} o\:* {behavior:url(#default#VML);} w\:* {behavior:url(#default#VM ...

  10. Linux shell 脚本攻略之统计文件的行数、单词数和字符数

    摘自:<Linux shell 脚本攻略>

随机推荐

  1. 宝塔linux面板防护CC设置

    使用宝塔linux面板很多用户受到CC攻击不知如何防范. 下面讲下如何利用宝塔自带的功能来进行基本的CC防护. 首先是在nginx上有个waf安全模块,里面有CC防护设置.(要求nginx为1.12版 ...

  2. 突破JAVA万人面试,懂多线程者得天下

    突破JAVA万人面试,懂多线程者得天下   在面试中,求职者会遇到很多棘手的问题,其中关于"线程安全"."线程同步"."线程死锁"等方面的面 ...

  3. Linux相关介绍和安装

    目录 前言 第一章 linux介绍 1.1 linux简介 第二章 Linux起源 2.1 Unix的历史 2.2 Unix操作系统的革命 2.3 Linux系统的诞生 2.4 Linux系统的发展史 ...

  4. ThreadX——IPC应用之信号量

    一.应用简介 在RTOS的应用开发中,信号量也是经常使用到的一种用于多任务之间信息同步.资源互斥访问的一种手段,常用于协调多个任务访问同一资源的场景.信号量又分为计数信号量和互斥信号量.计数信号量可以 ...

  5. MySQL最经典50道练习题

    表名和字段 学生表 Student(s_id,s_name,s_birth,s_sex):学生编号.学生姓名.出生年月.学生性别. 课程表 Course(c_id,c_name,t_id):课程编号. ...

  6. 精尽Spring MVC源码分析 - HandlerAdapter 组件(三)之 HandlerMethodArgumentResolver

    该系列文档是本人在学习 Spring MVC 的源码过程中总结下来的,可能对读者不太友好,请结合我的源码注释 Spring MVC 源码分析 GitHub 地址 进行阅读 Spring 版本:5.2. ...

  7. sqli-labs第5关布尔盲注pyhton脚本

    import requests import os #此函数先判断数据库长度 def length(url,str): num = 1 while True: str_num = '%d' %num ...

  8. 安装篇四:安装NGINX(1.4.0版本)

    #1.NGINX安装 1.安装文件上传软件 [root@TEST ~]# yum install lrzsz –y <---拖拽文件 2.检查软件安装的系统环境 [root@TEST ~]# c ...

  9. Centos7 搭建openldap完整详细教程(真实可用)

    最近,由于公司需求,需要搭建openldap来统一用户名和密码,目前市面上几乎所有的工具都支持ldap协议,具体ldap的介绍这里就不详细说明了,这里主要记录一下如果部署openldap来实现Ldap ...

  10. Net Core(Net5) 部署到不同操作系统遇到问题的解决方法

    Net Core(Net5) 部署到不同操作系统的解决方法 目录 Net Core(Net5) 部署到不同操作系统的解决方法 1 系统版本升级补丁 1.1应用程序部署时VC无法安装,导致应用程序缺少配 ...