shell实现多并发控制

背景:

遇到一个业务需求,一个上位机需要向多个下位机传送文件,当前的实现是for循环遍历所有下位机,传送文件,但是此种方法耗时太久,需要优化。因此可以通过并发的方式向下位机传送文件。

这边写一段测试代码,就简单输出一些内容。(输出1-10数字)

注意:以下每个点都是依次递进的。

实现过程:

1. 通过串行的方式实现

遍历1-10,然后将其输出(中间每个操作休眠1s,最终耗时10s)

# 串行执行命令
echo -e "串行执行:"
all_num=10
a=$(date +%H%M%S)

for ((i=1;i<=${all_num};i++))
do
{
	sleep 1
	echo ${i}
}
done

b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

结果:

shell实现多并发控制_第1张图片

 当前问题:

输出1-10,是10个循环,是按照顺序执行的,每一个循环1s,那么10个循环是10s,那如果需要输出1-1000呢?就是需要1000s,效率过于低下,所以需要并发执行。

2. 使用&,以及wait并发执行

知识储备:

符号&:在命令的末尾加上&,表示该命令在后台执行,后面的命令就无需等待这条命令执行完就可以执行。

wait:将wait之前的命令都执行结束(当前进程下的子进程都执行结束),才开始wait之后后面的语句。

代码:(耗时1s)

# 2.并发执行(&符号表示{}内的命令将在后台执行,后面的命令不用等前面的命令执行完就可以执行了)
# (wait是将当前脚本进行下的子进程都执行结束,然后在执行后面的语句)

echo -e "并发执行:"
all_num=10
a=$(date +%H%M%S)

for ((i=1;i<=${all_num};i++))
do
{
	sleep 1
	echo ${i}
}&
done

wait

b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

结果:

shell实现多并发控制_第2张图片

由此可以发现,并发执行确实在时间上优化了很多,但是有需要考虑一个问题:如果现在有1000个任务,那么后台就需要并发1000个任务(一下创建出1000个子进程),这样对系统造成非常大的压力,并发任务数量增多,操作系统的处理速度就会变慢,甚至出现其他不稳定因素。所以,最好是可以控制并发数(控制子进程的数量)。

3. 使用文件描述符和管道控制并发数

知识储备:

管道特性:管道默认是阻塞的,当进程从管道中读取数据,如果没有数据则进程会阻塞;

                当一个进程往管道中不断地写入数据但是没有进程去读取数据,此时只要管道没有满是可以写的,但若管道放满数据的则会报错。

有名管道:它是一种文件类型,在文件系统中可以看到。

利用有名管道的上述特性就可以实现一个队列控制了。

你可以这样想:一个公共厕所总共就10个蹲位,这个蹲位就是队列长度,厕所门口放着10把钥匙,要想上厕所必须拿一把药匙,上完厕所后归还药匙,下一个人就可以拿药匙进去上厕所了,这样同时来了1000个人上厕所,那前十个人抢到药匙进去上厕所了,后面的990人需要等一个人出来归还药匙才可以拿到药匙进去上厕所,这样10把药匙就实现了控制1000人上厕所的任务(os中称之为信号量)。

管道具有存一个读一个,读完一个就少一个,没有则阻塞,放回的可以重复取,这正是队列特性,但是问题是当往管道文件里面放入一段内容,没人取则会阻塞,这样你永远也没办法往管道里面同时放入10段内容(想成10把药匙),解决这个问题的关键就是文件描述符了。

创建有名管道文件,创建文件描述符关联管道文件,这时候这个文件描述符就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符: &2可以执行n次echo >&2 往管道里放入n把钥匙

 代码:

# 3.并发执行(&符号表示{}内的命令将在后台执行,后面的命令不用等前面的命令执行完就可以执行了)
# 控制并发数量(根据自己的当前业务需求)

echo -e "并发执行(控制并发数量,利用管道,文件描述符):"
all_num=10
# 并发的进程数
thread_num=5
a=$(date +%H%M%S)

# mkfifo 创建有名管道
myfifo="fd1"
mkfifo=${myfifo}

# 创建文件描述符,以可读(<)和可写(>)的方式关联管道文件,这时候文件描述符2就有了有名管道文件的所有特性
# 关联之后的文件描述符拥有管道文件的所有特性,所以可以将管道文件进行删除,之后使用文件描述符2即可
exec 2<>${myfifo}
rm -f ${myfifo}

# 为文件描述符创建占位信息(&2表述文件描述符2,往管道里面放入了一个“令牌”,相当于最多可以开启有thread_num个进程)
for ((i=1;i<=${thread_num};i++))
do
{
  echo >&2
}
done

# 命令
for ((i=1;i<=${all_num};i++))
do
# 代表从管道中获取一个令牌(一共有thread_num个进程,现在使用一个进程,2是代表文件描述符)
read -u2
{
	sleep 1
	echo ${i}
    # 代表执行完当前命令,将这个令牌又放入到管道中(使用完这个进程,将这个进程又返还回去)
    echo >&2 
}&
done

wait

# 命令执行完成,关闭文件描述符的读写
exec 2<&-
exec 2>&-
b=$(date +%H%M%S)
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

结果:

shell实现多并发控制_第3张图片

这种方法没有第二种方法快,但是比方法一块,这样是既可以提高效率,又实现了并发控制。

4. 使用xargs -P控制并发数

知识储备:

-I:执行多条命令(具体使用请查下,这里只是简单普及) -I后面的{},输出是sh -c中的内容,它代表seq 1 ${all_num},即1-10

-n:指定每行字符数

-P:表示支持的最大进程数,默认为1。为0时表示尽可能地大

代码:

# 4.并发执行(使用xargs -P控制并发数)

all_num=10
thread_num=5
 
a=$(date +%H%M%S)

# cp -rf /root/result /root/test-patch/ | xargs -n 1 -I {} -P ${thread_num} sh -c
seq 1 ${all_num} | xargs -n 1 -I {} -P ${thread_num} sh -c "sleep 1;echo {}"
 
b=$(date +%H%M%S)
 
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

结果:

shell实现多并发控制_第4张图片

5.  使用parallel控制并发数

 知识储备:

-j 是控制并发数(command1 ::: arg1 arg2 arg3)。

并行执行command1命令,并使用:::符号传递arg1、arg2和arg3参数。

代码:

# 5.并发执行(使用parallel控制并发数)
all_num=10
thread_num=6
 
a=$(date +%H%M%S)
 
parallel -j 5 "sleep 1;echo {}" ::: `seq 1 10`
 
b=$(date +%H%M%S)
 
echo -e "startTime:\t$a"
echo -e "endTime:\t$b"

结果:因为当前电脑没有安装parallel ,就不贴结果了。

参考:shell队列实现线程并发控制_shell并发执行命令_宗而研之的博客-CSDN博客

【Linux】shell脚本实现多并发_shell 多线程并发执行_zclinux_的博客-CSDN博客

你可能感兴趣的:(java,服务器,开发语言)