shell并发
原文链接:https://www.cnblogs.com/shiyublog/p/13561100.html
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)
有名管道特性:
-
mkfifo /tmp/fd1 创建有名管道
cat /tmp/fd1 显示管道中内容,如果管道内容为空,则阻塞

- 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...省略

参考链接:
3. exec操作文件描述符:
4. 文件描述符: