• shell实现控制进程并发数


    想要实现控制进程并发数,需要先了解下什么是文件描述符和命名管道

    文件描述符

    介绍

    文件描述符是一个非负整数,内核需要通过文件描述符来访问文件。当我们在系统中打开已有的文件或新建文件时,内核每次都会给特定的进程返回一个文件描述符,通过这个文件描述符来对文件进行读写操作。系统中内核默认会为每个进程初始创建三个标准的文件描述符,分别是0(标准输入)1(标准输出)2(标准错误)。可通过ll /proc//fd查看指定进程所拥有的所有文件描述符

    文件描述符操作

    语法

    创建:exec 文件描述符 <> 文件名

    使用:&文件描述符

    删除:exec 文件描述符<&-exec 文件描述符>&-

    使用示例
    # 创建仅可输入的文件描述符
    exec 1000>test.txt # 创建仅可输入的文件描述符
    echo hello >&1000  # 通过文件描述符写入数据
    echo world >&1000
    cat test.txt       # 查看是否写入成功
    hello
    world
    cat <&1000           # 从仅输入文件描述符读取数据失败
    cat: -: Bad file descriptor
    exec 1000<&-         # 关闭文件描述符
    
    # 创建仅可输出的文件描述符
    touch test2.txt      # 创建文件(输入文件描述符会自动创建文件,输出文件描述符不可以)
    exec 1001<test2.txt  # 创建仅可输出的文件描述符
    echo hello >&1001    # 通过文件描述符写入数据报错
    -bash: echo: write error: Bad file descriptor
    exec 1000<&-         # 关闭文件描述符
    
    # 创建可读写的文件描述符
    exec 1003<>test.txt  # 创建可读写的文件描述符
    cat <&1003           # 通过文件描述符读取数据
    hello
    world
    echo hi >&1003       # 通过文件描述符写入数据
    cat test.txt         # 查看内容
    hello
    world
    hi
    exec 1000<&-         # 关闭文件描述符
    
    # 数据丢失案例
    echo "init" > new.txt
    exec 1000>new.txt    # 创建仅输入文件描述符
    echo "end" >&1000    # 通过文件描述符写入数据
    exec 1000<&-         # 关闭文件描述符
    cat new.txt          # 验证
    end
    
    # 通过追加解决数据丢失问题
    exec 1000>>new.txt  # 通过文件描述符追加数据
    echo "start" >&1000
    exec 1000<&-
    cat new.txt
    end
    start
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    通过read读取数据

    通过read -u命令可以通过文件描述符获取文件内容,不过与cat命令查看全部文件内容不同,read命令默认一次仅读取一行数据,可以通过-n选项指定读取行数。

    示例:

    cat test.txt         # 准备一个有数据的文件
    line1
    line2
    line3
    exec 2000<text.txt   # 创建仅输出述符
    read -u2000 text     # 读取一行并赋值给text变量
    echo $text           # 查看变量
    line1
    read -u2000 text     # 读取一行并赋值给text变量
    echo $text           # 查看变量
    line2
    read -u2000 text     # 读取一行并赋值给text变量
    echo $text           # 查看变量
    line3
    read -u2000 text     # 当内容读取完后如果继续读取
    echo $text           # 查看变量
                         # 结果为空
    cat <&2000           # 从文件描述符里读取所有数据
                         # 结果同样为空
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    通过以上演示可以得出,文件描述符并不是简单的对应一个文件,文件描述符中还包含有很多文件相关的信息,如权限、文件偏移量等。其中文件偏移量就像一个指针,默认指向第一行,当使用read读取一行后,指针指向下一行,以此类推,直到文件读取完毕。

    其实不仅read会导致指针偏移,cat <&100同样也是,不同的是read默认一次偏移一行,cat <&100直接会导致指针指到文件末尾。除了以上方式,通过文件描述符输入数据同样会导致指针偏移。

    提示:如果执行read -u2000后面不跟变量名同样会导致指针偏移

    命名管道

    管道是进程间通信的一种方式,使用|会创建一个匿名管道,但是匿名管道只能实现父进程与子进程之间的数据交换,想要实现无关的进程间通信就需要命名管道,也叫FIFO文件(First In First Out 先进先出)。命名管道有如下几个特性

    • FIFO文件可以通过mknodmkfifo命令创建
    • 写入管道的数据一旦被读取后,就不可以再重复读取
    • 进程往命名管道中写入数据时,如果管道中没有数据,则写进程会被阻塞
    • 进程从命名管道中读取数据时,如果管道中没有数据,则读进程会被阻塞
    • 命名管道中的数据常驻内存,并不实际写入磁盘,读写效率会更高

    示例:

    mkfifo pipefile                 # 创建命名管道,不指定权限
    mkfifo -m 664 pipefile2         # 创建命名管道,并指定权限
    ls -l pipefile pipefile2        # 查看文件权限
    prw-r--r-- 1 root root   0 Nov 17 11:01 pipefile
    prw-rw-r-- 1 root root   0 Nov 17 11:21 pipefile2
    echo "hello world" > pipefile   # 写阻塞
    cat pipefile                    # 读数据,并解除写阻塞
    hello world
    cat pipefile                    # 读阻塞
    echo "hello fifo" > pipefile    # 写数据,并解除读阻塞
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    控制进程并发数

    通过文件描述符和命名管道就能实现控制进程的并发数,实现方式如下脚本所示。

    #!/bin/bash
    
    # 创建命名管道和文件描述符
    mkfifo ch3
    exec 12<> ch3
    # 删除ch3文件防止下次执行影响执行(删除后不影响文件描述符使用)
    rm -f ch3
    
    # 通过文件描述符往命名管道中写入5行任意数据,用于控制进程数量
    for i in {1..5};do
        echo "" >&12
    done
    
    # 实现循环执行20次sleep命令,执行过程中保持同时有5个进程并发执行
    # 首次并发执行5个进程后,这时的命名管道ch3中数据量是0条,会造成阻塞
    # read -u12 表示每次从命名管道读取一行
    # {...} & 表示括号内的一组命令后台执行
    # echo "" >&12 表示当一个sleep进程执行完成后会往命名管道里添加一行新的数据
    # 这时命名管道ch3中数据量是1条,会取消阻塞启动一个新的sleep进程
    # 启动之后,ch3中数据量是0条,继续造成阻塞,当有新的sleep命令执行完成后,就会有新的sleep进程执行
    # 循环往复,直到执行20次sleep命令
    
    for j in {1..20};do
        read -u12 
        { 
            echo "start sleep $j"
            sleep 5
            echo "stop sleep $j"
            echo "" >&12
        } &
    done
    # 等待所有后台进程执行完成
    wait
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    执行结果如下

    start sleep 1
    start sleep 5
    start sleep 2
    start sleep 3
    start sleep 4
    stop sleep 5
    stop sleep 1
    start sleep 6
    start sleep 7
    stop sleep 2
    stop sleep 4
    stop sleep 3
    start sleep 8
    start sleep 9
    start sleep 10
    stop sleep 6
    start sleep 11
    stop sleep 7
    start sleep 12
    stop sleep 8
    stop sleep 9
    stop sleep 10
    start sleep 13
    start sleep 15
    start sleep 14
    stop sleep 11
    start sleep 16
    stop sleep 12
    start sleep 17
    stop sleep 13
    stop sleep 15
    start sleep 18
    start sleep 19
    stop sleep 14
    start sleep 20
    stop sleep 16
    stop sleep 17
    stop sleep 19
    stop sleep 18
    stop sleep 20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    参考链接

    此文参考了丁明一大佬所著《Linux Shell核心编程指南》

  • 相关阅读:
    虚假KeePass网站利用Google Ads和Punycode推送恶意软件
    322.零钱兑换
    ES6-03-模版字符串、对象的简化写法
    关于400G光模块的常见问题解答
    2.树莓派4b+ubuntu18.04(ros版本melodic)+arduino mega自制两轮差速小车,实现建图导航功能
    宝塔面板搭建网站教程:Linux下使用宝塔一键搭建网站,内网穿透发布公网上线
    Java面试之场景题汇总
    Vue2使用定时器和闭包实现防抖和节流函数。将函数放入util.js中,供具体功能在methods中调用
    浏览器发送请求的方法
    C/C++苹果和虫子 2019年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析
  • 原文地址:https://blog.csdn.net/weixin_44208042/article/details/134464252