【说解】在shell中通过mkfifo创建命名管道来控制多个进程并发执行
背景:
工作中有两个异地机房需要传数据,数据全名很规范,在某个目录下命名为统一的前缀加上编号。如/path/from/file.{1..100}。而机房间的专线对单个scp进程的传输速度是有限制的,比如最大在100Mb/s,如果直接启动100个scp,则又会遇到ssh的并发连接数限制。
所以需要控制并发数,即不超过ssh的并发限制,又要让单网卡上的带宽接近饱和,尽快完成传输(假设专线带宽远大于单机网卡带宽)
实现
之前知道通过mkfifo创建一个命名管道,可以实现对并发的控制。现在来实现一个。
在此之前,如果对mkfifo不了解,可以参考这个连接,作者写得很详细,我就不造轮子了。
这里直接给出代码,并做一些解释。因为单进程的带宽如上所述,所以考虑9个并发。代码如下:
#!/bin/bash your_func()
{ # use your cmd or func instead of sleep here. don't end with background(&)
date +%s
echo "scp HOSTNAME:/home/USER/path/from/file.$1 REMOTE_HOST:/home/USER/path/to/"
sleep
} concurrent()
{ # from $ to $, (included $,$ itself), con-current $ cmd
start=$ && end=$ && cur_num=$ # ff_file which is opened by fd will be really removed after script stopped
mkfifo ./fifo.$$ && exec <> ./fifo.$$ && rm -f ./fifo.$$ # initial fifo: write $cur_num line to $ff_file
for ((i=$start; i<$cur_num+$start; i++)); do
echo "init time add $i" >&
done for((i=$start; i<=$end; i++)); do
read -u # read from mkfifo file
{ # REPLY is var for read
echo -e "-- current loop: [cmd id: $i ; fifo id: $REPLY ]" your_func $i
echo "real time add $(($i+$cur_num))" >& # write to $ff_file
} & # & to backgroud each process in {}
done
wait # wait all con-current cmd in { } been running over
} concurrent
上面以3为并发数,执行0到8号共9次,以便显示如下执行结果。
bash concurrent.sh
-- current loop: [cmd id: ; fifo id: init time add ]
-- current loop: [cmd id: ; fifo id: init time add ]
-- current loop: [cmd id: ; fifo id: init time add ] scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/
scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/ scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/
-- current loop: [cmd id: ; fifo id: real time add ]
-- current loop: [cmd id: ; fifo id: real time add ]
-- current loop: [cmd id: ; fifo id: real time add ] scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/ scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/
scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/
-- current loop: [cmd id: ; fifo id: real time add ]
-- current loop: [cmd id: ; fifo id: real time add ]
-- current loop: [cmd id: ; fifo id: real time add ] scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/ scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/
scp HOSTNAME:/home/USER/path/from/file. REMOTE_HOST:/home/USER/path/to/
从date输出的时间上,可以看出,每2秒会执行3个并发。
说明
整体过程
设N的值为并发数。通过在fifo中初始化N行内容(可以为空值),再利用fifo的特性,从fifo中每读一行,启动一次your_func调用,当fifo读完N次时,fifo为空。再读时就会阻塞。这样开始执行时就是N个并发(1-N)。
当并发执行的进程your_func,任意一个完成操作时,下一步会招待如下语句:
echo "real time add $(($i+$cur_num))" 1>&4
这样就对fifo新写入了一行,前面被阻塞的第N+1号待执行的进程read成功,开始进入{}语句块执行。这样通过read fifo的阻塞功能,实现了并发数的控制。
需要注意的是,当并发数较大时,多个并发进程即使在使用sleep相同秒数模拟时,也会存在进程调度的顺序问题,因而并不是按启动顺序结束的,可能会后启动的进程先结束。
从而导致如下语句所示的输出中,两个数字并不一定是相等的。并发数越大,这种差异性越大。
-- current loop: [cmd id: 8 ; fifo id: real time add 9 ]
自定义函数
修改自定义函数your_func,这个函数实际只需要一行就完成了。
your_func()
{ # use your cmd or func instead of sleep here. don't end with background(&)
date +%s
scp HOSTNAME:/home/USER/path/from/file.$ REMOTE_HOST:/home/USER/path/to/
}
需要注意的是,scp命令最后不需要添加压后台的&符号。因为在上一级就已经压后台并发了。
再来说明concurrent函数的第14行。
exec digit<> filename
这是一个平常很少使用到的命令。特别是‘<>’这个符号。既然不明白我们来查一下系统帮助。
man bash
# search 'exec ' Opening File Descriptors for Reading and Writing
The redirection operator [n]<>word causes the file whose name is the expansion of word to be opened for both reading and writing on file
descriptor n, or on file descriptor if n is not specified. If the file does not exist, it is created.
通过man bash来搜索exec加空格,会找到对exec的说明。注意如果直接man exec,会搜索到linux programer's manual,是对execl, execlp, execle, execv, execvp, execvpe - execute a file这一堆系统函数的调用说明。
还要注意哦,4<> 这几个字符不要加空格,必然连着写。word前可以加空格。
rm file
mkfifo先创建管道文件,再通过exec将该文件绑定到文件描述符4。也许你在疑惑后面的rm操作。其实当该文件绑定到文件描述符后,内核已经通过open系统调用打开了该文件,这个时候执行rm操作,删除的是文件的Inode,但concurrent函数已经连接到文件的block块区。
如果你遇到过这样的情况,你就明白了:如果线上的nginx日志是没有切分的,access.log会越来越大,这时你直接rm access.log文件后,文件不见了,但df查看系统并没有释放磁盘空间。这就是因为rm只是删除了inode,但这之前nginx早已经通过open打开了这个文件,nginx进程的进程控制块中的文件描述符表中对应的fd,已经有相应的文件指针指向了该文件在内存中的文件表,以及其在内存中的v节点表,并最终指向文件的实际存储块。因此nginx依然可以继续写日志,磁盘还在被写入。只有重启或者reload,让进程重新读一次配置,重新打开一遍相应的文件时,才会发现该文件不存在的,并新建该文件。而这时因为Inode节点已经释放,再用df查看时就能看到可用空间增大了。
不懂可以参考APUE的图3.1及想着说明。
因此14行的rm并不影响后继脚本执行,直到脚本结束,系统收回所有文件描述符。
初始化
18-20行在做初始化管道的工作。其中读取管道有两类写法:
# style
for ((i=$start; i<$cur_num+$start; i++)); do
echo "init time add $i" >&
done # style
for ((i=$start; i<$cur_num+$start; i++)); do
echo "init time add $i"
done >&
差别就是‘>&4’ 这几个字符放在echo语句后面,还是放在done后面,两者都可以,前者针对echo语句,后者针对整个for循环。
同理,在下一个for循环中,read命令也有两种方式:
# style
for((i=$start; i<=$end; i++)); do
read -u
{
your_func $i
echo "real time add $(($i+$cur_num))" >& # write to $ff_file
} &
done # style
for((i=$start; i<=$end; i++)); do
read
{
your_func $i
echo "real time add $(($i+$cur_num))" >& # write to $ff_file
} &
done <&
关于REPLY
再解释一下REPLY变量。这是上述循环中,用来存放read命令从fifo中读到的内容。其实在整个脚本中,是不需要关注这个点的。不过这里随带也解释一下。
通过能fifo的不断读写,才实现了echo如下语句:
-- current loop: [cmd id: 7 ; fifo id: real time add 7 ]
如何了解到REPLY呢?我们又得man一下了。为了找到read的参数。先man read发现不对。再如下查找,因为read是bash自建命令。
man bash
# search 'Shell Variables' REPLY Set to the line of input read by the read builtin command when no arguments are supplied.
【说解】在shell中通过mkfifo创建命名管道来控制多个进程并发执行的更多相关文章
- 【linux】mkfifo 命令创建命名管道实现进程之间通信
mkfifo 命令 mkfifo命令创建一个FIFO特殊文件,是一个命名管道(可以用来做进程之间通信的桥梁) 管道也是一种文件,一般是linux中的一个页大小,4k,管道数据一旦被读取就没了.(管道大 ...
- Linux shell中的I/O重定向相关(转)
1. 基本概念(这是理解后面的知识的前提,请务必理解) a. I/O重定向通常与 FD有关,shell的FD通常为10个,即 0-9: b. 常用FD有3个,为0(stdin,标准输入).1(std ...
- Linux Shell中管道的原理及C实现框架
在shell中我们经常用到管道,有没考虑过Shell是怎么实现管道的呢? cat minicom.log | grep "error" 标准输入.标准输出与管道 我们知道,每一个进 ...
- shell 匿名管道和命名管道
管道的特点:如果管道中没有数据,那么取管道数据的操作就会滞留,直到管道内进入数据,然后读出后才会终止这一操作:同理,写入管道的操作如果没有读取管道的操作,这一动作也会滞留. 1,匿名管道 匿名管道使用 ...
- Linux进程间通信(四):命名管道 mkfifo()、open()、read()、close()
在前一篇文章—— Linux进程间通信 -- 使用匿名管道 中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关 ...
- shell 命名管道,进程间通信
命名管道基础 命名管道也被称为FIFO文件, 在文件系统中是可见的,并且跟其它文件一样可以读写! 命名管道特点: 当写进程向管道中写数据的时候,如果没有进程读取这些数据,写进程会堵塞 当读取管道中的数 ...
- shell 命名管道,进程间通信, ncat作http server
命名管道基础 命名管道也被称为FIFO文件, 在文件系统中是可见的,并且跟其它文件一样可以读写! 命名管道特点: 当写进程向管道中写数据的时候,如果没有进程读取这些数据,写进程会堵塞 当读取管道中的数 ...
- 命名管道FIFO和mkfifo函数
进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的.如果要互相通信的几个进程没有从公共祖先那里继承文件描述符,它们怎么通 ...
- 本地方法中printf如何传给java--java系统级命名管道
本地方法中printf如何传给java--java系统级命名管道 摘自:https://blog.csdn.net/dog250/article/details/6007301 2010年11月13日 ...
随机推荐
- Shell替换
如果表达式中包含特殊字符,Shell 将会进行替换.例如,在双引号中使用变量就是一种替换,转义字符也是一种替换. #!/bin/bash a= echo -e "Value of a is ...
- 【每日一linux命令3】参数(或称选项)顺序
一般除了特殊情况,参数是没有顺序的.举例而言,输入"–a –v"与输入"–v –a"以及"–av" 的执行效果是相同的.但若该参数后指定了要 ...
- Redis/HBase/Tair比较
KV系统对比表 对比维度 Redis Redis Cluster Medis Hbase Tair 访问模式 支持Value大小 理论上不超过1GB(建议不超过1MB) 理论上可配置(默认配置1 ...
- Android权限管理之Permission权限机制及使用
前言: 最近突然喜欢上一句诗:"宠辱不惊,看庭前花开花落:去留无意,望天空云卷云舒." 哈哈~,这个和今天的主题无关,最近只要不学习总觉得生活中少了点什么,所以想着围绕着最近面试过 ...
- Xamarin与Visual stuido2015离线安装包分享
最近看见大伙留言才知道国内安装Xamarin开发原来这么艰辛啊! 第一:网速不快 第二:Android SDK下载受限 等等... 鉴于这些原因,特写下这篇文章以及分享打包好的离线包以帮助大家尽快体验 ...
- Mysql存储引擎及选择方法
0x00 Mysql数据库常用存储引擎 Mysql数据库是一款开源的数据库,支持多种存储引擎的选择,比如目前最常用的存储引擎有:MyISAM,InnoDB,Memory等. MyISAM存储引擎 My ...
- [C#] C# 知识回顾 - 特性 Attribute
C# 知识回顾 - 特性 Attribute [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5911289.html 目录 特性简介 使用特性 特性 ...
- IT持续集成之质量管理
研发工具生态 质量相关工作 一次编译产出测试包与上线包 !从源头保证版本的⼀一致性!代码质量控制! 全⽅方位的⾃自动化测试体系保证! 提测冒烟效率! 全⾃自动上线流程杜绝⼈人⼯工犯错! 生产环境应⽤用 ...
- BPM与 SAP & Oracle EBS集成解决方案分享
一.需求分析 SAP和Oracle EBS都是作为全球顶级的的ERP产 品,得到了众多客户的青睐.然而由于系统庞大.价格昂贵以及定位不同,客户在实施过程中经常会面临以下困惑: 1.SAP如何实现&qu ...
- 海鑫智圣:物联网漫谈之MQTT协议
什么是MQTT协议 MQTT(消息队列遥测传输协议)是IBM在1999年专门针对物联网等应用场景来制订的轻量级双向消息传输协议,它主要是为了解决物联网上使用到的设备的互相通信的问题,以及这些设备与后端 ...