Shell多进程执行任务

简介: 管道就像水管,有流入才会有流出,水管数水流的通道,管道是数据的通道。管道分为无名管道和有名管道。 无名管道:常用的|就是管道,只不过是无名的,可以直接作为两个进程的数据通道,比如:cat file.txt | grep test 有名管道:mkfilo 可以创建一个管道文件,比如:mkfiflo testfifo 管道有一个特点,如果管道中没有数据,那么取管道数据的操作就会阻塞,直到管道内进入数据,然后读出后才会终止这一操作,同理,写入管道的操作如果没有读取操作,这一个动作也会阻塞。

展示代码

#!/bin/bash

trap "exec 1000>&-;exec 1000<&-;exit 0" 2

# 分别为 创建管道文件,文件操作符绑定,删除管道文件
mkfifo testfifo
exec 1000<>testfifo
rm -rf testfifo

# 对文件操作符进行写入操作。 
# 通过一个for循环写入10个空行,这个10就是我们要定义的后台线程数量。
for ((n=1; n<=10; n++))
do
    echo >&1000
done

# 创建一个备份目录
if [[ ! -d back ]]; then
    mkdir back
fi

# 开始时间记录
start=`date "+%s"`
# 获取URL总数,如果总数为0,直接退出
total=`cat urls | wc -l`
if [[ $total == 0 ]]; then
    echo 'urls总数为空'
    exit 0
fi 

# 遍历URLS文件,开始执行下载
for ((i=1;i<=$total;i++))
do {
    # 从testfifo中读取一行
    read -u1000
    {    
        # 增加尝试次数,最大5次
        for j in {1..5}
        do 
            # 判断单独进程文件目录是否存在,不存在则创建目录
            download_dir=audio"$i"
            if [[ ! -d $download_dir ]]; then
                mkdir -p $download_dir
            fi
            echo "往目录${download_dir}中开始下载文件,尝试次数:${j}"
            # 读取URLS中的一行,下载文件
            you-get -o $download_dir `sed -n "$i"p urls | tr -d '\r'`

            # 校验是否有异常,如果没有异常,则跳出循环,执行外下一条,如果有异常,再次尝试下载
            if [[ $? != 0 ]]; then
                mv $download_dir/* back
                rm -rf $download_dir
            else
                break
            fi
        done
        # 向文件操作符中写入一个空行
        echo >&1000
    }&
}
done

# 等待所有任务完成
wait
end=`date "+%s"`
echo "time: `expr $end - $start`"

exec 1000>&-
exec 1000<&-

所谓多进程,就是将一个任务划分成多个子任务放在后台执行。"FIFO"是一种特殊的文件类型,它允许独立的进程通讯. 一个进程打开FIFO文件进行写操作,而另一个进程对之进行读操作, 然后数据便可以如同在shell或者其它地方常见的的匿名管道一样流线执行。默认情况下,创建的FIFO的模式为0666('a+rw')减去umask中设置的位。

串行、并行

串行任务

为了比较并行和串行的区别,我们先写个串行的脚本:

#!/bin/bash

start=`date "+%s"`
for i in {1..10}
do
    echo $i;
    sleep 2
done
end=`date "+%s"`
echo "Time: `expr $end - $start`"

执行结果如下:

$ sh series.sh 
1
2
3
4
5
6
7
8
9
10
Time: 21 

从结果来开,执行完上面10次任务,每次任务2秒,总耗时21秒,符合代码的逻辑。

并行任务

先将上面的串行任务改成多线程并行任务。

#!/bin/bash

start=`date "+%s"`
for i in {1..10}
do 
    {
        echo $i;
        sleep 2
    }&
done
wait
end=`date "+%s"`
echo "Time: `expr $end - $start`"

执行上面脚本的结果如下:

$ sh paralle.sh 
1
2
3
4
5
6
7
8
9
10
Time: 2

通常,用{}将不占处理器却很耗时的任务放包装一个块,通过&放置在后台运行,以达到节约时间的效果。上面并行代码,我们把10次任务全部放在后台执行,每个人物耗时2秒,由于并行执行,总耗时也就是Max(任务耗时)=2秒。

{
    echo $i;
    sleep 2
}&

在小任务跟前,这种方式运用起来得心应手,但是在任务量过大的时候,这种方式的缺点也就显而易见了:无法控制运行在后台的进程数,不能就10万个任务就是跑10万个进程吧。为了控制进程,我们引入了管道文件操作符

管道、文件操作符

管道

管道就像水管,有流入才会有流出,水管数水流的通道,管道是数据的通道。管道分为无名管道和有名管道。

管道类别 命令 栗子
无名管道 常用的` `就是管道,只不过是无名的,可以直接作为两个进程的数据通道 `echo "hello world, I'm a test" grep "test"`
有名管道 mkfilo 可以创建一个管道文件 mkfiflo testfifo

管道有一个特点,如果管道中没有数据,那么取管道数据的操作就会阻塞,直到管道内进入数据,然后读出后才会终止这一操作,同理,写入管道的操作如果没有读取操作,这一个动作也会阻塞。

管道符写入阻塞

当通过echo命令往fifotest管道中写入数据时,由于没有任何其他消费进程对管道操作,所以,该管道阻塞,直到再打开一个窗口且通过cat操作该管道。
管道符写入读取

同理,先操作读取管道也会出现阻塞的情况。
管道符读取阻塞

通过以上实验,看以看到,仅仅一个管道文件似乎很难实现控制后台线程数,因此我们接下来简单介绍 文件操作符

文件操作符

系统运行起始,就相应设备自动绑定到了 三个文件操作符 分别为012 对应 stdinstdoutstderr 。在 /proc/self/fd 或者/dev/fd中可以看到这三个对应文件:

linux fd

输出到这三个文件的内容都会显示出来。只是因为显示器作为最常用的输出设备而被绑定。

在Linux中,可以通过exec指令自行定义、绑定文件操作符,文件操作符一般从3~(n-1)都可以随便使用,此处的nulimit -n的定义值。

ulimit -n

从上图可以看出本机的n值为8192 ,所以文件操作符只能使用0-8192 ,可自行定义的就只能是3-8192

虽然exec和source都是在父进程中直接执行,但exec这个与source有很大的区别,source是执行shell脚本,而且执行后会返回以前的shell。而exec的执行不会返回以前的shell了,而是直接把以前登陆shell作为一个程序看待,在其上经行复制。

exec可参考此文:《linux 下的 mkfifo、exec 命令使用

代码分析

第3行:

  • 接受信号 2 (ctrl +C)做的操作。
  • 我们生成文件描述符并做绑定时,可以用exec 1000<>testfifo来实现,但关闭时必须分开来写。
  • >读的绑定,< 标识写的绑定 <> 则标识对文件描述符1000的所有操作,其等同于对管道文件testfifo的操作。

第6-8行:  

  • 分别为 创建管道文件文件操作符绑定删除管道文件
  • 可能会有疑问,为什么不能直接使用管道文件呢?事实上,这并非多此一举,刚才已经说明了管道文件的一个重要特性了,那就是读写必须同时存在,缺少某一种操作,另一种操作就是阻塞,而绑定文件操作符正好解决了这个问题。

    第12-15行:

  • 对文件操作符进行写入操作。 通过一个 for 循环写入 10 个空行,这个 10 就是我们要定义的后台线程数量
  • 为什么写入空行而不是 10 个字符呢?这是因为,管道文件的读取是以为单位的。
  • 当我们试图用 read 读取管道中的一个字符时,结果是不成功的,上面的例子已经证实了使用cat是可以读取的。

第32-61行:

  • 遍历urls的总行数,循环处理url
  • 25-29行是读取urls文件的总行数的逻辑(看开篇代码)。
  • 这里我们有$total个任务($total是变量,是读取的urls的总行数,值大于0),我们需要保证后台只有10个进程在同步运行(当然这段代码有点小遗憾,就是未能根据总行数决定用多少个进程,加入总行数小于10,但我们创建了10行空字符串,但这并不影响我们的测试) 。
  • read -u1000 的作用是:读取一次管道中的一行,在这儿就是读取一个空行。
  • 减少操作附中的一个空行之后,执行一次任务(当然是放到后台执行),需要注意的是,这个任务在后台执行结束以后会向文件操作符中写入一个空行,这就是重点所在,如果我们不在某种情况某种时刻向操作符中写入空行,那么结果就是:在后台放入10个任务之后,由于操作符中没有可读取的空行,导致read -u1000这儿始终停顿。
  • 第38-56行,处理自己的业务,这里面是通过you-get下载url中的图片、语音,如果下载失败,最多尝试5次。关于you-get参考这篇文章《You-Get:支持 80 多个网站的命令行多媒体下载器》了解其更多。

第64-69行:

  • 等待所有进程执行结束。
  • exec 1000>&-exec 1000<&-是关闭 fd1000

该文首发《虚怀若谷》个人博客,转载前请务必署名,转载请标明出处。

古之善为道者,微妙玄通,深不可识。夫唯不可识,故强为之容:

豫兮若冬涉川,犹兮若畏四邻,俨兮其若客,涣兮若冰之释,敦兮其若朴,旷兮其若谷,混兮其若浊。

孰能浊以静之徐清?孰能安以动之徐生?

保此道不欲盈。夫唯不盈,故能敝而新成。

请关注我的微信公众号:下雨就像弹钢琴,Thanks(・ω・)ノ
微信二维码

目录
相关文章
|
6月前
|
Shell Linux 调度
【Shell 命令集合 系统管理 】Linux 调整进程优先级 renice命令 使用指南
【Shell 命令集合 系统管理 】Linux 调整进程优先级 renice命令 使用指南
122 0
|
6月前
|
存储 监控 Linux
【Shell 命令集合 系统管理 】⭐⭐⭐Linux 查看当前正在运行的进程信息 ps命令 使用指南
【Shell 命令集合 系统管理 】⭐⭐⭐Linux 查看当前正在运行的进程信息 ps命令 使用指南
110 0
|
6月前
|
监控 Shell Linux
【Shell 命令集合 系统管理 】⭐⭐⭐Linux 向进程发送信号 kill命令 使用指南
【Shell 命令集合 系统管理 】⭐⭐⭐Linux 向进程发送信号 kill命令 使用指南
88 0
|
30天前
|
存储 运维 监控
自动化运维:使用Shell脚本简化日常任务
【9月更文挑战第35天】在IT运维的日常工作中,重复性的任务往往消耗大量的时间。本文将介绍如何通过编写简单的Shell脚本来自动化这些日常任务,从而提升效率。我们将一起探索Shell脚本的基础语法,并通过实际案例展示如何应用这些知识来创建有用的自动化工具。无论你是新手还是有一定经验的运维人员,这篇文章都会为你提供新的视角和技巧,让你的工作更加轻松。
29 2
|
4月前
|
Shell Linux
shell 同时执行多任务下载视频
shell 同时执行多任务下载视频
49 4
|
3月前
|
Linux Perl
在Linux中,系统目前有许多正在运行的任务,在不重启机器的条件下,有什么方法可以把所有正在运行的进程移除呢?
在Linux中,系统目前有许多正在运行的任务,在不重启机器的条件下,有什么方法可以把所有正在运行的进程移除呢?
|
5月前
|
Java 程序员
Java多线程编程是指在一个进程中创建并运行多个线程,每个线程执行不同的任务,并行地工作,以达到提高效率的目的
【6月更文挑战第18天】Java多线程提升效率,通过synchronized关键字、Lock接口和原子变量实现同步互斥。synchronized控制共享资源访问,基于对象内置锁。Lock接口提供更灵活的锁管理,需手动解锁。原子变量类(如AtomicInteger)支持无锁的原子操作,减少性能影响。
46 3
|
5月前
|
Shell UED Python
Shell 循环语句:重复任务的自动化利器
在Shell脚本中,循环语句如`while`和`for`是自动化任务的关键。`while`循环在条件满足时执行,例如计算1到100的和;`for-in`循环遍历列表,可用于迭代指定数值或命令输出,如求1到100的和。`select-in`循环提供交互式菜单,增强脚本用户体验。理解并运用这些循环能提升脚本效率和可读性。现在,动手试试吧!
47 2
|
5月前
|
SQL 关系型数据库 MySQL
实时计算 Flink版产品使用问题之运行run-application --target kubernetes-application执行,通过进程的返回码来决定作业是否成功,任务返回码都是0,该怎么办
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
5月前
|
Unix Shell Perl
技术心得:实例解析shell子进程(subshell)
技术心得:实例解析shell子进程(subshell)