shell实现控制进程并发数

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

文件描述符

介绍

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

文件描述符操作

语法

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

使用:&文件描述符

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

使用示例
bash 复制代码
# 创建仅可输入的文件描述符
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
通过read读取数据

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

示例:

bash 复制代码
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           # 从文件描述符里读取所有数据
                     # 结果同样为空

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

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

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

命名管道

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

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

示例:

bash 复制代码
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    # 写数据,并解除读阻塞

控制进程并发数

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

bash 复制代码
#!/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

执行结果如下

bash 复制代码
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

参考链接

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

相关推荐
vvw&几秒前
如何在 Ubuntu 22.04 上安装并开始使用 RabbitMQ
java·linux·运维·服务器·spring·ubuntu·rabbitmq
皓月盈江1 小时前
Linux Debian安装ClamAV和命令行扫描病毒方法,以及用Linux Shell编写了一个批量扫描病毒的脚本
linux·运维·ubuntu·debian·clamav·开源杀毒
steveqobs1 小时前
Debian-linux运维-locale配置(locale failed)
linux·运维·debian
hahaqi95271 小时前
uniapp生成h5后发布到服务器碰到的问题解决
运维·服务器·uni-app
女王の专属领地1 小时前
产品初探Devops!以及AI如何赋能Devops?
运维·devops
m0_748235952 小时前
【rustdesk】客户端和服务端的安装和部署(自建服务器,docker,远程控制开源软件rustdesk)
运维·服务器·docker
Channing Lewis2 小时前
服务器广播算法
运维·服务器·算法
Dan淡淡的心2 小时前
软路由系统 iStoreOS 中部署 Minecraft 服务器
运维·服务器·docker·我的世界
梁萌3 小时前
Docker中的分层(Layer)
运维·docker·容器
IT 古月方源3 小时前
关于 VRRP的详解
运维·网络·tcp/ip·网络安全·智能路由器