shell 队列实现线程并发控制
需求:并发检测1000台web服务器状态(或者并发为1000台web服务器分发文件等)如何用shell实现?
方案一:(这应该是大多数人都第一时间想到的方法吧)
思路:一个for循环1000次,顺序执行1000次任务。
#!/bin/bash
#
start_time=`date +%s` #定义脚本运行的开始时间
for ((i=;i<=;i++))
do
sleep #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
echo 'success'$i;
done
stop_time=`date +%s` #定义脚本运行的结束时间
echo "TIME:`expr $stop_time - $start_time`"
运行结果:
[root@iZ94yyzmpgvZ ~]# ./test.sh
success1
success2
success3
success4
success5
success6
success7
........此处省略
success999
success1000
TIME:
代码解析以及问题:
一个for循环1000次相当于需要处理1000个任务,循环体用sleep 1代表运行一条命令需要的时间,用success$i来标示每条任务.
这样写的问题是,1000条命令都是顺序执行的,完全是阻塞时的运行,假如每条命令的运行时间是1秒的话,那么1000条命令的运行时间是1000秒,效率相当低,而我的要求是并发检测1000台web的存活,如果采用这种顺序的方式,那么假如我有1000台web,这时候第900台机器挂掉了,检测到这台机器状态所需要的时间就是900s,吃屎都吃不上热乎的。
所以,问题的关键集中在一点:如何并发
方案二:
思路:一个for循环1000次,循环体里面的每个任务都放入后台运行(在命令后面加&符号代表后台运行)。
实现:
#!/bin/bash
#
start=`date +%s` #定义脚本运行的开始时间
for ((i=;i<=;i++))
do
{
sleep #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
echo 'success'$i;
}& #用{}把循环体括起来,后加一个&符号,代表每次循环都把命令放入后台运行
#一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理了
#循环了1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理
#这些任务
done
wait #wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再
#往下执行。
#在这里写wait是因为,一条命令一旦被放入后台后,这条任务就交给了操作系统
#shell脚本会继续往下运行(也就是说:shell脚本里面一旦碰到&符号就只管把它
#前面的命令放入后台就算完成任务了,具体执行交给操作系统去做,脚本会继续
#往下执行),所以要在这个位置加上wait命令,等待操作系统执行完所有后台命令
end=`date +%s` #定义脚本运行的结束时间
echo "TIME:`expr $end - $start`"
运行结果:
[root@iZ94yyzmpgvZ /]# ./test1.sh
......
[] Done { sleep ; echo 'success'$i; }
[] Done { sleep ; echo 'success'$i; }
success992
[] Done { sleep ; echo 'success'$i; }
[] Done { sleep ; echo 'success'$i; }
success993
[] Done { sleep ; echo 'success'$i; }
success994
success995
[] Done { sleep ; echo 'success'$i; }
success996
[] Done { sleep ; echo 'success'$i; }
[] Done { sleep ; echo 'success'$i; }
success997
success998
[] Done { sleep ; echo 'success'$i; }
success999
[] Done { sleep ; echo 'success'$i; }
[]- Done { sleep ; echo 'success'$i; }
success1000
[]+ Done { sleep ; echo 'success'$i; }
TIME:
代码解析以及问题:
shell中实现并发,就是把循环体的命令用&符号放入后台运行,1000个任务就会并发1000个线程,运行时间2s,比起方案一的1000s,已经非常快了。
可以看到输出结果success4 ...success3完全都是无序的,因为大家都是后台运行的,这时候就是cpu随机运行了,所以并没有什么顺序
这样写确实可以实现并发,然后,大家可以想象一下,1000个任务就要并发1000个线程,这样对操作系统造成的压力非常大,它会随着并发任务数的增多,操作系统处理速度会变慢甚至出现其他不稳定因素,就好比你在对nginx调优后,你认为你的nginx理论上最大可以支持1w并发了,实际上呢,你的系统会随着高并发压力会不断攀升,处理速度会越来越慢(你以为你扛着500斤的东西你还能跑的跟原来一样快吗)
方案三:
思路:基于方案二,使用linux管道文件特性制作队列,控制线程数目
知识储备:
一、管道文件
1、无名管道(ps aux | grep nginx)
2、有名管道(mkfifo /tmp/fd1)
有名管道特性:
1)、cat /tmp/fd1(如果管道内容为空,则阻塞)
实验:
2)、echo "test" > /tmp/fd1(如果没有读管道的操作,则阻塞)
总结:利用有名管道的上述特性就可以实现一个队列控制了
你可以这样想:
一个女士公共厕所总共就10个蹲位,这个蹲位就是队列长度,女厕 所门口放着10把药匙,要想上厕所必须拿一把药匙,上完厕所后归还药匙,下一个人就可以拿药匙进去上厕所了,这样同时来了1千位美女上厕所,那前十个人抢到药匙进去上厕所了,后面的990人需要等一个人出来归还药匙才可以拿到药匙进去上厕所,这样10把药匙就实现了控制1000人上厕所的任务(os中称之为信号量)
二、文件描述符
1、管道具有存一个读一个,读完一个就少一个,没有则阻塞,放回的可以重复取,这正是队列
特性,但是问题是当往管道文件里面放入一段内容,没人取则会阻塞,这样你永远也没办法
往管道里面同时放入10段内容(想当与10把药匙),解决这个问题的关键就是文件描述符了。
2、mkfifo /tmp/fd1
创建有名管道文件exec 3<>/tmp/fd1,创建文件描述符3关联管道文件,这时候3这个文件描述符就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符: &3可以执行n次echo >&3 往管道里放入n把钥匙
exec命令用法:http://blog.sina.com.cn/s/blog_7099ca0b0100nby8.html
实现:
#!/bin/bash
#
start_time=`date +%s` #定义脚本运行的开始时间
[ -e /tmp/fd1 ] || mkfifo /tmp/fd1 #创建有名管道
exec <>/tmp/fd1 #创建文件描述符,以可读(<)可写(>)的方式关联管道文件,这时候文件描述符3就有了有名管道文件的所有特性
rm -rf /tmp/fd1 #关联后的文件描述符拥有管道文件的所有特性,所以这时候管道文件可以删除,我们留下文件描述符来用就可以了
for ((i=;i<=;i++))
do
echo >& #&3代表引用文件描述符3,这条命令代表往管道里面放入了一个"令牌"
done for ((i=;i<=;i++))
do
read -u3 #代表从管道中读取一个令牌
{
sleep #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
echo 'success'$i
echo >& #代表我这一次命令执行到最后,把令牌放回管道
}&
done
wait stop_time=`date +%s` #定义脚本运行的结束时间
echo "TIME:`expr $stop_time - $start_time`"
exec <&- #关闭文件描述符的读
exec >&- #关闭文件描述符的写
运行结果:
[root@iZ94yyzmpgvZ /]# ./test2.sh
success4
success6
success7
success8
success9
success5
......
success935
success941
success942
......
success992
[] Done { sleep ; echo 'success'$i; echo >&; }
success993
[] Done { sleep ; echo 'success'$i; echo >&; }
success994
[] Done { sleep ; echo 'success'$i; echo >&; }
success998
success999
success1000
success997
success995
success996
[] Done { sleep ; echo 'success'$i; echo >&; }
TIME:
代码解析以及问题:
两个for循环,第一个for循环10次,相当于在女士公共厕所门口放了10把钥匙,第二个for循环1000次,相当于1000个人来上厕所,read -u3 相当于取走一把药匙,{} 里面最后一行代码 echo >&3 相当于上完厕所送还药匙。
这样就实现了10把药匙控制1000个任务的运行,运行时间为101s,肯定不如方案二快,但是比方案一已经快很多了,这就是队列控制同一时间只有最多10个线程的并发,既提高了效率,又实现了并发控制。
注意:创建一个文件描述符exec 3<>/tmp/fd1 不能有空格,代表文件描述符3有可读(<)可写(>)权限,注意,打开的时候可以写在一起,关闭的时候必须分开关,exec 3<&- 关闭读,exec 3>&- 关闭写
想一下:
1、假如一台优化后的nginx单机可以接受最大的并发是1w,那么同时来了2w人,那nginx是怎么做的
2、或者,一家餐厅有10个服务员,餐厅的资金的支持它最多可以再招收90个服务员(apache可设置默认开始10个进程,最大可开启100个进程),也就是说这家餐厅最多可以有100个服务员。那现在同时有1000个人来吃饭,这时候会出现的现象是:前10个人会立马有人招待吃饭,然后餐厅每招收一个新服务员就有一个人可以坐下来吃饭,好,假设现在总共有100个服务员在提供服务了,那么还剩下900人如何处理,很简单,吃完一个出去一个,出去一个就进来一个吃饭。这样就实现了100个服务员去为1000个人提供服务。
实际上就是用一个100人的队列去控制1000个任务的运行,同一时间最多并发100个线程,这样既节省了系统资源又提高了效率
原文:http://egon09.blog.51cto.com/9161406/1754317
shell 队列实现线程并发控制的更多相关文章
- shell队列实现线程并发控制(转)
需求:并发检测1000台web服务器状态(或者并发为1000台web服务器分发文件等)如何用shell实现? 方案一:(这应该是大多数人都第一时间想到的方法吧) 思路:一个for循环1000次,顺序执 ...
- 拆开Ceph看队列和线程
作者:吴香伟 发表于 2017/01/08 版权声明:可以任意转载,转载时务必以超链接形式标明文章原始出处和作者信息以及版权声明 我上小学时家离学校很远,家在某某山脚,学校在镇里.每周回家一趟,周五放 ...
- 自定义ThreadPoolExecutor带Queue缓冲队列的线程池 + JMeter模拟并发下单请求
.原文:https://blog.csdn.net/u011677147/article/details/80271174 拓展: https://github.com/jwpttcg66/GameT ...
- python队列、线程、进程、协程
目录: 一.queue 二.线程 基本使用 线程锁 自定义线程池 生产者消费者模型(队列) 三.进程 基本使用 进程锁 进程数据共享 默认数据不共享 queues array Manager.dict ...
- 6、TensorFlow基础(四)队列和线程
队列和线程 和 TensorFlow 中的其他组件一样,队列(queue)本身也是图中的一个节点,是一种有状态的节点,其他节点,如入队节点(enqueue)和出队节点(dequeue),可以修改它的内 ...
- python队列、线程、进程、协程(转)
原文地址: http://www.cnblogs.com/wangqiaomei/p/5682669.html 一.queue 二.线程 #基本使用 #线程锁 #自定义线程池 #生产者消费者模型(队列 ...
- 【Java并发】并发队列与线程池
并发队列 阻塞队列与非阻塞队 ConcurrentLinkedQueue BlockingQueue ArrayBlockingQueue LinkedBlockingQueue PriorityBl ...
- 多线程多进程学习threading,queue线程安全队列,线程间数据状态读取。threading.local() threading.RLock()
http://www.cnblogs.com/alex3714/articles/5230609.html python的多线程是通过上下文切换实现的,只能利用一核CPU,不适合CPU密集操作型任务, ...
- 我们一起来学Shell - shell的并发及并发控制
文章目录 bash的并发 未使用并发的脚本 简单修改 使用wait命令 控制并发进程的数量 文件描述符 查看当前进程打开的文件 自定义当前进程用描述符号操作文件 管道 我们一起来学Shell - 初识 ...
随机推荐
- .Net Core自动化部署系列(三):使用GitLab CI/CD 自动部署Api到Docker
之前写过使用Jenkins实现自动化部署,最近正好没事研究了下GitLab的自动化部署,顺便记录一下. 使用GitLab部署我们需要准备两件事,第一个起码你得有个GitLab,自己搭建或者使用官方的都 ...
- 让你如绅士般基于描述编写 Python 命令行工具的开源项目:docopt
作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...
- dubbo配置负载均衡、集群环境
再用dubbo作为项目架构的时候,给consumer消费者用nginx提供了负载均衡策略和集群的实现, 但是想了下,consumer再多,但是提供者还是一个,最后还不都是落到了这一个provider上 ...
- link 和 @import 的区别是什么?
link语法结构: <link href="url" rel="stylesheet" type="text/css"> @im ...
- day 19作业
目录 今日作业: 今日作业: 1.什么是对象?什么是类? 答:对象是特征与技能的集合体,类是一系列对象相同的特征与技能的结合体 2.绑定方法的有什么特点 答:由对象来调用称之为对象的绑定方法,不同的对 ...
- JVM Java字节码的角度分析switch的实现
目录 Java字节码的角度分析switch的实现 引子 前置知识 一个妥协而又枯燥的方案 switch的实现 回顾历史 字节码分析 其他实现方式? Java字节码的角度分析switch的实现 作者 k ...
- 微信退款(APP和公众号一样)
/** * 将xml转为array * @param string $xml xml字符串 * @return array 转换得到的数组 */ public function xml2array($ ...
- 带你涨姿势的认识一下 Kafka
Kafka 基本概述 什么是 Kafka Kafka 是一个分布式流式平台,它有三个关键能力 订阅发布记录流,它类似于企业中的消息队列 或 企业消息传递系统 以容错的方式存储记录流 实时记录流 Kaf ...
- CH3801Rainbow的信号
Description Freda发明了传呼机之后,rainbow进一步改进了传呼机发送信息所使用的信号.由于现在是数字.信息时代,rainbow发明的信号用N个自然数表示.为了避免两个人的对话被大坏 ...
- Mysql综述(1)数据是如何读存的
引言 我们都知道,mysql中的索引,事务,锁等都是作为开发人员要重点掌握的知识面,但要想掌握理解好这些知识却并非易事. 其中原因之一就是这些概念都过于抽象,事实上如果都不懂mysql数据是以一种怎样 ...