目录
1. for循环执行任务
-
一个for循环1000次顺序执行1000次任务
#!/bin/bash start_time=`date +%s` # 定义脚本运行的开始时间 for ((i=1;i<=1000;i++)) do sleep 1 # sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替) echo 'success'$i; done stop_time=`date +%s` # 定义脚本运行的结束时间 echo "TIME:`expr $stop_time - $start_time`" # 执行脚本输出结果 success1 success2 success3 ... ... success1000 TIME:1000
脚本解析及问题:
解析:一个for循环1000次相当于需要处理1000个任务,循环sleep 1代表运行一个命令需要的时间,用 success$i 来标示每条任务。
问题:1000条命令都是顺序执行的,完成是阻塞时的运行,假如每条命令的运行时间是1秒的话,那么1000条命令的运行时间是1000秒,效率相当低,而要求是并发检测1000台web的存活,如果采用这种顺序的方式,那么假如我有1000台web,这时候第900台机器挂掉了,检测到这台机器状态所需要的时间就是900s!
2. 全并发执行任务
-
一个for循环1000次,循环体里面的每个任务放入后台运行(在命令后面加&符号代表后台运行)
#!/bin/bash start_time=`date +%s` # 定义脚本运行的开始时间 for ((i=1;i<=1000;i++)) do { sleep 1 # sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替) echo 'success'$i; } & done wait stop_time=`date +%s` # 定义脚本运行的结束时间 echo "TIME:`expr $stop_time - $start_time`" # 执行脚本输出结果 success726 success697 .... ... success979 success963 success981 success987 TIME:1
脚本解析及问题:
解析:shell实现并发,1000个任务就会并发1000个线程,运行时间2s,比起方案一的1000s,已经非常快了
{ ... } &
把循环体的命令用&符号放入后台运行,一旦放入后台,就意味着{}
里面的命令交给操作系统的一个线程处理
wait
命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再往下执行,在这里写wait是因为,一条命令一旦被放入后台后,这条任务就交给了操作系统,shell脚本会继续往下运行(也就是说:shell脚本里面一旦碰到&符号就只管把它前面的命令放入后台就算完成任务了,具体执行交给操作系统去做,脚本会继续往下执行),所以要在这个位置加上wait命令,等待操作系统执行完所有后台命令问题:可以看出输出结果完全都是无序的,因为大家都是后台运行的,这个时候就是CPU随机运行了,所以并没有什么顺序。
这样写确实可以实现并发,然后1000个任务就要并发1000个线程,这样对操作系统造成的压力非常大,它会随着并发任务数的增多,操作系统处理速度会慢甚至出现其他不稳定因素,就好比你在对nginx调优后,你认为你的nginx理论上最大可以支持1w并发,实际上呢,你的系统会随着高并发压力会不断攀升,处理速度会越来越慢
3. 并发控制
-
基于方案二:使用linux管道文件特性制作队列,控制线程数目。
-
管道文件fifo
# 开两个终端 # 终端1 [root@localhost ~]# mkfifo /tmp/fd1 # 创建管道文件 [root@localhost ~]# cat /tmp/fd1 # cat管道文件会在阻塞状态 # 终端2 [root@localhost ~]# echo "test" > /tmp/fd1 # 向管道文件输入内容 # 终端1 [root@localhost ~]# cat /tmp/fd1 test # 读取到输入的文件,停止阻塞并输出内容 [root@localhost ~]# cat /tmp/fd1 # cat管道文件会再次进入阻塞状态,等待管道文件输入内容
管道具有存一个读一个,读完一个就少一个,没有则阻塞,放回的可以重复取,这正是队列特性,但是问题是当往管道文件里面放入一段内容,没人取则阻塞,这样你永远也没办法往管道里面同时放入10段内容(相当与10把钥匙),解决这个问题的关键就是文件描述符
-
文件描述符3 (3不是固定 可以写100等)
创建有名管道文件
exec 3<>/tmp/fd1
,创建文件描述符3关联管道文件,这时候3
这个文件描述符就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道是否为空。也不用关心是否有内容写入引用文件描述符:&3
可以执行n次echo >&3
往管道里放入n段内容 -
read -u3
读取文件描述符关联管道中的内容
-
关闭文件描述符的读和写
exec 3<&-
#!/bin/bash start_time=`date +%s` [ -e /tmp/fd1 ] || mkfifo /tmp/fd1 # 创建管道 exec 3<>/tmp/fd1 # 创建文件描述符,以可读(<)可写(>)的方式关联管道文件,这时候文件描述符3就有了有名管道文件的所有特性 rm -rf /tmp/fd1 # 关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,我们留下文件描述符来用就可以了 for ((i=1;i<=100;i++)) do echo >&3 # &3代表引用文件描述符3,这条命令代表往管道里面放入了一个"令牌" done for ((i=1;i<=1000;i++)) do read -u3 # 代表从管道中读取一个令牌 { sleep 1 echo 'success'$i echo >&3 # 代表我这一次命令执行到最后,把令牌放回管道 }& done wait stop_time=`date +%s` echo "TIME:`expr $stop_time - $start_time`" exec 3<&- # 关闭文件描述符的读 exec 3>&- # 关闭文件描述符的写 # 执行脚本输出结果 输出100条执行结果 --------------停顿 输出100条执行结果 --------------停顿 ... TIME:10
-
脚本解析:
标签:文件,shell,管道,fd1,并发,描述符,1000 From: https://www.cnblogs.com/ican97/p/17777542.html
两个for循环
- 第一个for循环10次,相当于放了100把钥匙
- 第二个for循环1000次,相当于1000个人来执行任务
read -u3
相当于取走一把钥匙。{...}
里面最后一行代码echo >&3
执行完任务还钥匙。这样就实现了100把钥匙控制1000个任务的运行,运行时间为10s,肯定不如方案二快,但是比方案一已经快很多了,这就是队列控制同一时间只有最多100个线程的并发,即提高了效率,又实现了并发控制。
注意: 创建一个文件描述符
exec 3<>/tmp/fd1
不能有空格,代表文件描述符3有可读(<)可写(>)
权限,注意,打开的时候可以写在一起,关闭的时候必须分开,exec 3<&-关闭读,exec 3>&-关闭写
。