多线程任务调度与线程等待
1.基础知识介绍
1.1 linux后台进程
Unix是一个多任务系统,允许多用户同时运行多个程序。shell的元字符&
提供了在后台运行不需要键盘输入的程序的方法。输入命令后,其后紧跟&
字符,该命令就会被送往到linux后台执行,而终端又可以继续输入下一个命令了。
1.2 linux文件描述符
文件描述符(缩写fd)在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
1.3. linux管道
在Unix或类Unix操作系统中,管道是一个由标准输入输出链接起来的进程集合,因此,每一个进程的输出将直接作为下一个进程的输入,
linux管道包含两种:
- 匿名管道
- 命名管道
管道有一个特点,如果管道中没有数据,那么取管道数据的操作就会滞留,直到管道内进入数据,然后读出后才会终止这一操作;同理,写入管道的操作如果没有读取管道的操作,这一动作就会滞留。
1.3.1. 匿名管道
在Unix或类Unix操作系统的命令行中,匿名管道使用ASCII中垂直线|
作为匿名管道符,匿名管道的两端是两个普通的,匿名的,打开的文件描述符:一个只读端和一个只写端,这就让其它进程无法连接到该匿名管道。
1.3.2. 命名管道(FIFO,First In First Out)
命名管道也称FIFO,从语义上来讲,FIFO其实与匿名管道类似,但值得注意:
- 在文件系统中,FIFO拥有名称,并且是以设备特俗文件的形式存在的;
- 任何进程都可以通过FIFO共享数据;
- 除非FIFO两端同时有读与写的进程,否则FIFO的数据流通将会阻塞;
- 匿名管道是由shell自动创建的,存在于内核中;而FIFO则是由程序创建的(比如
mkfifo
命令),存在于文件系统中; - 匿名管道是单向的字节流,而FIFO则是双向的字节流;
2.需求描述
背景:linux主机调度任务,由于主机资源有限,为了充分利用可用资源,通常采用多进程执行任务通常采用后台执行。
方案一:任务可以调用进程数为30,使用&后台执行30个进程,常见方式则等待30个进程执行完毕之后,再启动下一批30个进程。此方案无法保持常驻线程数为30,未能充分利用资源。
方案二:介于方案一无法保持常驻进程数为30,故考虑以队列的形式去实现进程常驻。
实现如下:
#!/bin/bash
## 场景:两个任务,必须要求任务一先执行完毕,再执行任务二
## 实现方法:先将任务一添加到队列中,等待任务一执行完毕后,再将任务二添加到队列中
function task {
arg=${1}
# 为了方便观察队列执行,进程1和25睡眠2秒
if [ ${arg} == "1" ]||[ ${arg} == "25" ]
then
echo "$arg sleep 20s"
sleep 2s
fi
echo "执行任务: ${arg}"
}
# 创建队列和锁
mkfifo list
mkfifo lock
# 插入数据不阻塞
exec 3<>list
exec 4<>lock
# 任务1
for ((i=0; i<20; i++)) do
echo "task ${i} in list"
echo "task ${i}" >&3
done
# 释放锁,即向锁管道中加一行
echo >&4
# 开启4个进程
threads=4
for ((i=0; i<${threads}; i++)); do
{
# 获取锁,即读取管道中的一行,每次读取管道就会减少一行
while read -t 1 -u 4 && read -t 1 -u 3 execCommand; do
# 释放锁,即向锁管道中加一行
echo >&4
# 任务代码
echo "${execCommand} out list"
${execCommand}
done
} &
done
wait
echo "task 1-20 exec over"
## 任务2
for ((i=25; i<30; i++)) do
echo "task ${i} in list"
echo "task ${i}" >&3
done
# 释放锁,即向锁管道中加一行
echo >&4
# 开启4个进程
for ((i=0; i<${threads}; i++)); do
{
# 获取锁,即读取管道中的一行,每次读取管道就会减少一行
while read -t 1 -u 4 && read -t 1 -u 3 execCommand; do
# 释放锁,即向锁管道中加一行
echo >&4
# 任务代码
echo "${execCommand} out list"
${execCommand}
done
} &
done
wait
echo "task 25-30 exec over"
# 关闭队列和文件描述符
# exec 3>&-;表示关闭文件描述符3的写
# exec 3<&-;表示关闭文件描述符3的读
exec 3>&-
exec 4>&-
rm -rf lock
rm -rf list
执行结果
[rot@aliyun_centos8 shell]# sh fifot.sh
task 0 in list
task 1 in list
task 2 in list
task 3 in list
task 4 in list
task 5 in list
task 6 in list
task 7 in list
task 8 in list
task 9 in list
task 10 in list
task 11 in list
task 12 in list
task 13 in list
task 14 in list
task 15 in list
task 16 in list
task 17 in list
task 18 in list
task 19 in list
task 0 out list
执行任务: 0
task 1 out list
1 sleep 2s
task 2 out list
执行任务: 2
task 3 out list
执行任务: 3
task 4 out list
执行任务: 4
task 5 out list
执行任务: 5
task 6 out list
执行任务: 6
task 7 out list
执行任务: 7
task 8 out list
执行任务: 8
task 9 out list
执行任务: 9
task 10 out list
执行任务: 10
task 11 out list
执行任务: 11
task 12 out list
执行任务: 12
task 13 out list
执行任务: 13
task 14 out list
执行任务: 14
task 15 out list
执行任务: 15
task 16 out list
执行任务: 16
task 17 out list
执行任务: 17
task 18 out list
执行任务: 18
task 19 out list
执行任务: 19
执行任务: 1
task 1-20 exec over
task 25 in list
task 26 in list
task 27 in list
task 28 in list
task 29 in list
task 25 out list
25 sleep 2s
task 26 out list
执行任务: 26
task 27 out list
执行任务: 27
task 28 out list
执行任务: 28
task 29 out list
执行任务: 29
执行任务: 25
task 25-30 exec over