打赏

[Shell] shell并发

1. for循环并发执行 - 前台命令变后台进程

shell中,后一个前台命令必须等待前一个前台命令执行完毕才能进行,这就是所谓的单线程程序。
shell并没有真正意义上的多进程。而最简单的节省时间,达到“多线程”效果的办法,是将前台命令变成后台进程,这样一来就可以跳过前台命令的限制了。

1.1 用法

for((i=0;i<$num_tasks;i++))
{
}&
done
wait
echo "done"

1.2 问题

以上的实现方法中,同时有num_tasks在后台运行,如果num_tasks个数非常大,那么很可能爆内存。那么如果协调内存和并发效率呢?

1.3 解决方法

控制同时启动的进程的个数。

2. 控制并发执行的进程个数 - 管道 + 文件操作符实现队列

2.1 Prerequisities

2.1.1 管道文件

1:无名管道(ps aux | grep nginx)

2:有名管道(mkfifo /tmp/fd1)

有名管道特性:

1. mkfifo /tmp/fd1 创建有名管道

   cat /tmp/fd1 显示管道中内容,如果管道内容为空,则阻塞

 

2. echo "test" > /tmp/fd1 如果没有读管道的操作,则阻塞

在terminal 1 中执行以下命令,向管道中输入'test',由于没有读管道的操作,所以被阻塞了

在terminal 2 中执行以下命令,读管道中的内容,于是terminal 1 结束了阻塞的状态。

terminal 2 有了输出test,而terminal 1中命令终止。

可以利用以上特性维护一个存令牌的队列,向其中写入一个令牌,被阻塞,只能这个令牌被读取之后,才可以结束阻塞的状态;

如果用于并发进程的控制,以上特性可以用于保证并发的进程数是1。

 

=====  但是,如果想每次并发多个进程要如何处理呢? =====

可以看出管道特性中,阻碍“并发多个”的点在于 “写入一个即阻塞”,即限制了令牌队列的长度为1;

那么如果可以不被阻塞,而可以由我们制定令牌长度,这样并发数就是我们指定的队列长度,也即队列中的初始写入的令牌数了。

如果希望实现“不被阻塞”,那么可以采用“文件描述符”~

2.1.2 文件描述符

1. 简介

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。(百度百科)

2. 文件描述符与管道文件关联

exec [文件描述符fid] <> [管道文件fd]  

该命令含义:创建文件描述符fid(非负整数,可以避开使用0 stdin, 1 stdout, 2 stderr),并关联(以读写方式 <> 打开)管道文件fd。

此时文件描述符fid就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符&fid。

可以执行n次echo >&fid 往管道里放入n个令牌。

2.2 shell 指定并发数的实现

2.2.1 实现逻辑

设置这个队列的长度为可以并发执行的进程个数$num_para。

(1) 先往这个管道中放置num_para个令牌;

(2) 来了num_para个进程,取走了num_para个令牌;第num_para + 1个进程因为取不到令牌,被阻塞;

(3) 最初取到令牌的进程中,有一个执行完了,释放令牌到管道中;

(4) 管道中又有令牌了,一个被阻塞的进程可以获取该令牌,执行;

(5) 循环(3)(4)直至所有进程执行完毕。

2.2.2 实现代码

start_time=`date +%s`                # 定义脚本运行的开始时间

[ -e /tmp/fd1 ] || mkfifo /tmp/fd1   # 如果有名管道文件不存在,则创建

exec 3<>/tmp/fd1                     # 创建文件描述符3, 并以可读<, 可写>的方式关联管道文件
                                     # 这时文件描述符3就有了有名管道文件的特性

rm -rf /tmp/fd1                      # 关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除
                                     # 留下文件描述符来用就可以了

for((i=1;i<=10;i++))                 # &3 引用文件描述符3,循环向管道中放入了10个令牌,支持并发数为10
do
    echo >&3
done

for((i=1;i<=100;i++))
do
read -u3                             # 从管道中读取1个令牌
{
    sleep 1                          # 模拟进程运行
    echo 'success'$i
    echo >&3                         # 该进程运行结束,将其令牌放回管道
}&
done
wait


stop_time=`date +%s`                 # 定义脚本运行的结束时间

echo "Time: `expr $stop_time - $start_time`"   # 获取执行100条进程总的运行时间

exec 3<&-                            # 关闭文件描述符的读 
exec 3>&-                            # 关闭文件描述符的写

输出:

success1...省略

 

 

参考链接:

1. shell队列实现线程并发控制:https://www.cnblogs.com/chenjiahe/p/6268853.html

2. shell实现多线程笔记:https://blog.51cto.com/mochaming/1279864

3. exec操作文件描述符:http://blog.sina.com.cn/s/blog_7099ca0b0100nby8.html

4. 文件描述符:https://blog.csdn.net/Captain_MXD/article/details/52153233

posted @ 2020-08-25 21:11  listenviolet  阅读(1461)  评论(0编辑  收藏  举报