[Shell] shell并发
1. for循环并发执行 - 前台命令变后台进程
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