shell中控制多个进程并发执行的方法

shell中实现多进程实际上就是将多个任务放到后台中执行而已,但是现在需要控制多进程并发的数量该如何实现呢?别急,我们一步一步来实现这个目标,首先从最原始的串行执行开始:

#!/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" 

执行结果:

# sh test1.sh
test
test
test
test
test
time: 10

这是最原始的一种处理方式,串行执行,无法有效利用计算机的资源,并且效率低下。如果可以一次执行多个任务的,把它放到后台即可,我们做如下改进:

#!/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):

  • 无名管道
    它经常出现在我们的命令中,例如cat /etc/passwd | awk -F: ‘{print $1}‘,其中"|"就是管道,它将前一个命令的结果输出到后一个进程中,作为两个进程的数据通道,不过他是无名的。
  • 有名管道
    使用mkfifo命令创建的管道即为有名管道,例如,mkfifo pipefile, pipefile即为有名管道。

管道有一个显著的特点,如果管道里没有数据,那么去取管道数据时,程序会阻塞住,直到管道内进入数据,然后读取才会终止这个操作,反之,管道在执行写入操作时,如果没有读取操作,同样会阻塞,下面是实例:

# mkfifo pipefile
# echo test > pipefile
# 此时会阻塞在这

此时我新开一个窗口再执行读操作

# cat pipefile
test

这时窗口一中的进程才会终止,再来看先读的情况:

# cat pipefile
# 同样阻塞停滞在此

新开窗口执行写操作:

# 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<&-

这样,就实现了进程的并发控制

原文地址:https://blog.51cto.com/hld1992/2370135

时间: 2024-08-29 11:02:07

shell中控制多个进程并发执行的方法的相关文章

【说解】在shell中通过mkfifo创建命名管道来控制多个进程并发执行

背景: 工作中有两个异地机房需要传数据,数据全名很规范,在某个目录下命名为统一的前缀加上编号.如/path/from/file.{1..100}.而机房间的专线对单个scp进程的传输速度是有限制的,比如最大在100Mb/s,如果直接启动100个scp,则又会遇到ssh的并发连接数限制. 所以需要控制并发数,即不超过ssh的并发限制,又要让单网卡上的带宽接近饱和,尽快完成传输(假设专线带宽远大于单机网卡带宽) 实现 之前知道通过mkfifo创建一个命名管道,可以实现对并发的控制.现在来实现一个.

(002)spring容器中bean初始化、销毁时执行的方法及其3种实现方式

spring容器中bean初始化或者销毁时会执行一些方法,有3种实现方式. 1.实现InitializingBean.DisposableBean接口,在bean的属性设置后和bean销毁时分别执行afterPropertiesSet和destroy方法 pom.xml文件 <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/

quartz中,同一个job的并发执行

假设,我们有个job,每2分钟执行一次,但是job本身就要执行5分钟,这个时候,quartz默认设置是并发的,所以它又会开一个线程来执行.这样往往会导致我们执行的数据不正确. 解决办法: 1.要是是和spring框架结合的,可以添加如下配置: <property name="concurrent" value="true" /> 2.要是没有使用spring框架,只需要在Job的实现类上加上一个注解就可以啦: @DisallowConcurrentExe

CentOS中ps配合Kill进程的N种方法

首先,用ps查看进程,方法如下: $ ps -ef  …… smx       1822     1  0 11:38 ?        00:00:49 gnome-terminal smx       1823  1822  0 11:38 ?        00:00:00 gnome-pty-helper smx       1824  1822  0 11:38 pts/0    00:00:02 bash smx       1827     1  4 11:38 ?       

Linux shell 中判断一个变量是否为空 的方法

判断一个脚本中的变量是否为空,我写了一个这样的shell脚本: #!/bin/sh #filename: test.sh para1= if [ ! -n $para1 ]; then echo "IS NULL" else echo "NOT NULL" fi 然后把该脚本:test.sh通过chmod +x 改为可以执行的脚本,执行后输出的结果为: NOT NULL,很是奇怪,最后,通过查询一些资料发现,可以通过如下方式判断一个shell变量是否为空: 1. 变

【转】VC 多线程中控制界面控件的几种方法

原文网址:https://software.intel.com/zh-cn/blogs/2010/11/30/vc-3 为了保证界面的用户体验经常要把数据处理等放到子线程中进行,然后把结果更新到主界面,通常有这样几种方法. 1. 启动线程时把控件关联变量的指针传参给线程函数,这种方法无疑是最简单的方法,但极容易造成访问异常,因为VC6中的控件都不是线程安全的. 2. 就是先进一点的方法,把控件的句柄传给线程函数,有时也不好用在子线程中通过SendNotifyMessage or PostMess

Oracle 11g 安装过程中“检查网络配置要求 未执行”解决方法

正在检查网络配置要求... 检查完成.此次检查的总体结果为: 未执行 网上查了一下,很多朋友都遇到这个问题而无从下手,其实解决起来很容易的. 只需要在 Windows XP 中安装 Microsoft LoopBack Adapter[Microsoft 环回适配器]就可以了. Microsoft 环回适配器是一种可用于在虚拟网络环境中进行测试的工具,在这样的环境中无法访问网络.另外,如果存在与网络适配器或网络适配器驱动程序的冲突,则必须使用环回适配器.可以将网络客户端.协议和其他网络配置项目绑

ubuntu下shell中切换普通解释器和anaconda解释器的方法

1)打开shell,输入: gedit ~/.bashrc 1 2)在文件最下面,找到: export PATH="/usr/local/anaconda2/bin:$PATH" 1 如果将这一行注释(前面加上#),则自动切换回默认python解释器:取消注释,就变回anaconda解释器. 3)命令行输入,重新载入一下这个文件: source ~/.bashrc 原文地址:https://www.cnblogs.com/USTBlxq/p/10488486.html

linux 进程、执行方式、程序、编程元素

1.进程: shell应用程序的进程一般为交互式的,在广义上分为GUI和CLI GUI(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面,支持鼠标,桌面等外设,比较著名的windows.Android CLI(command-line interface,命令行界面)是指可在用户提示符下键入可执行指令的界面,它通常不支持鼠标,用户通过键盘输入指令,计算机接收到指令后,予以执行. CLI的词法一般由 命令,选项,参数组成,分