背景:

工作中有两个异地机房需要传数据,数据全名很规范,在某个目录下命名为统一的前缀加上编号。如/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创建命名管道来控制多个进程并发执行的更多相关文章

  1. 【linux】mkfifo 命令创建命名管道实现进程之间通信

    mkfifo 命令 mkfifo命令创建一个FIFO特殊文件,是一个命名管道(可以用来做进程之间通信的桥梁) 管道也是一种文件,一般是linux中的一个页大小,4k,管道数据一旦被读取就没了.(管道大 ...

  2. Linux shell中的I/O重定向相关(转)

    1. 基本概念(这是理解后面的知识的前提,请务必理解)  a. I/O重定向通常与 FD有关,shell的FD通常为10个,即 0-9: b. 常用FD有3个,为0(stdin,标准输入).1(std ...

  3. Linux Shell中管道的原理及C实现框架

    在shell中我们经常用到管道,有没考虑过Shell是怎么实现管道的呢? cat minicom.log | grep "error" 标准输入.标准输出与管道 我们知道,每一个进 ...

  4. shell 匿名管道和命名管道

    管道的特点:如果管道中没有数据,那么取管道数据的操作就会滞留,直到管道内进入数据,然后读出后才会终止这一操作:同理,写入管道的操作如果没有读取管道的操作,这一动作也会滞留. 1,匿名管道 匿名管道使用 ...

  5. Linux进程间通信(四):命名管道 mkfifo()、open()、read()、close()

    在前一篇文章—— Linux进程间通信 -- 使用匿名管道 中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关 ...

  6. shell 命名管道,进程间通信

    命名管道基础 命名管道也被称为FIFO文件, 在文件系统中是可见的,并且跟其它文件一样可以读写! 命名管道特点: 当写进程向管道中写数据的时候,如果没有进程读取这些数据,写进程会堵塞 当读取管道中的数 ...

  7. shell 命名管道,进程间通信, ncat作http server

    命名管道基础 命名管道也被称为FIFO文件, 在文件系统中是可见的,并且跟其它文件一样可以读写! 命名管道特点: 当写进程向管道中写数据的时候,如果没有进程读取这些数据,写进程会堵塞 当读取管道中的数 ...

  8. 命名管道FIFO和mkfifo函数

    进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的.如果要互相通信的几个进程没有从公共祖先那里继承文件描述符,它们怎么通 ...

  9. 本地方法中printf如何传给java--java系统级命名管道

    本地方法中printf如何传给java--java系统级命名管道 摘自:https://blog.csdn.net/dog250/article/details/6007301 2010年11月13日 ...

随机推荐

  1. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  2. 在Sublime Text 3上安装代码格式化插件CodeFormatter

    1.了解CodeFormatter插件 在Sublime Text 3中编写代码,为了能让我们的代码格式变得漂亮整洁,需要一个能自动格式代码的插件.这里发现CodeFormatter插件不错,它能支持 ...

  3. 前端学HTTP之内容协商

    前面的话 一个URL常常需要代表若干不同的资源.例如那种需要以多种语言提供其内容的网站站点.如果某个站点有说法语的和说英语的两种用户,它可能想用这两种语言提供网站站点信息.理想情况下,服务器应当向英语 ...

  4. Angular企业级开发-AngularJS1.x学习路径

    博客目录 有链接的表明已经完成了,其他的正在建设中. 1.AngularJS简介 2.搭建Angular开发环境 3.Angular MVC实现 4.[Angular项目目录结构] 5.[SPA介绍] ...

  5. [C#] C# 知识回顾 - 序列化

    C# 知识回顾 -  序列化 [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5902005.html 目录 序列化的含义 通过序列化保存对象数据 众 ...

  6. js报错: Uncaught RangeError: Invalid string length

    在ajax请求后得到的json数据,遍历的时候chrome控制台报这个错误:Uncaught RangeError: Invalid string length,在stackoverflow查找答案时 ...

  7. System.Guid ToString五中格式

    参考:https://msdn.microsoft.com/en-us/library/97af8hh4.aspx 测试代码: using System; using System.Collectio ...

  8. EC笔记:第4部分:21、必须返回对象时,别返回引用

    使用应用可以大幅减少构造函数与析构函数的调用次数,但是引用不可以滥用. 如下: struct St { int a; }; St &func(){ St t; return t; } 在返回t ...

  9. 港真,到底应该选择OA还是BPM?

    越来越多企业意识到流程管理的重要性,但是,选择OA还是BPM,却让他们产生了选择困难症. 一方面,企业皆注重流程的高效运转,最好内外部的业务都能用一个系统来解决.所有流程一天就能上线什么的,那就更好啦 ...

  10. SQL Server事务、视图和索引

    废话不多说,直接上干货 14:13:23 事务 概括:事务是一种机制,一个操作序列,包含一组数据库操作命令,并且把所有的命令作为一个整体一起 向系统提交或撤销操作 请求. 事务的特性:   1.原子性 ...