设为首页 收藏本站
查看: 781|回复: 0

shell队列实现线程并发控制

[复制链接]
累计签到:2 天
连续签到:1 天
发表于 2016-3-24 09:04:45 | 显示全部楼层 |阅读模式
  需求:并发检测1000台web服务器状态(或者并发为1000台web服务器分发文件等)如何用shell实现?

    方案一:(这应该是大多数人都第一时间想到的方法吧)
思路:一个for循环1000次,顺序执行1000次任务。
实现:      

1
2
3
4
5
6
7
8
9
10
11
12
#!/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`"



运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
[iyunv@iZ94yyzmpgvZ ~]# . test.sh
success1
success2
success3
success4
success5
success6
success7
........此处省略
success999
success1000
TIME:1000




代码解析以及问题:
一个for循环1000次相当于需要处理1000个任务,循环体用sleep 1代表运行一条命令需要的时间,用success$i来标示每条任务.
这样写的问题是,1000条命令都是顺序执行的,完全是阻塞时的运行,假如每条命令的运行时间是1秒的话,那么1000条命令的运行时间是1000秒,效率相当低,而我的要求是并发检测1000台web的存活,如果采用这种顺序的方式,那么假如我有1000台web,这时候第900台机器挂掉了,检测到这台机器状态所需要的时间就是900s,吃屎都吃不上热乎的。
所以,问题的关键集中在一点:如何并发




    方案二:
思路:一个for循环1000次,循环体里面的每个任务都放入后台运行(在命令后面加&符号代表后台运行)。
实现:      

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash
start=`date +%s` #定义脚本运行的开始时间

for ((i=1;i<=1000;i++))
do
{
        sleep 1  #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
        echo 'success'$i;
}&              #用{}把循环体括起来,后加一个&符号,代表每次循环都把命令放入后台运行
                 #一旦放入后台,就意味着{}里面的命令交给操作系统的一个线程处理了
                 #循环了1000次,就有1000个&把任务放入后台,操作系统会并发1000个线程来处理
                 #这些任务         
done   
wait             #wait命令的意思是,等待(wait命令)上面的命令(放入后台的)都执行完毕了再
                 #往下执行。
                 #在这里写wait是因为,一条命令一旦被放入后台后,这条任务就交给了操作系统
                 #shell脚本会继续往下运行(也就是说:shell脚本里面一旦碰到&符号就只管把它
                 #前面的命令放入后台就算完成任务了,具体执行交给操作系统去做,脚本会继续
                 #往下执行),所以要在这个位置加上wait命令,等待操作系统执行完所有后台命令
end=`date +%s`  #定义脚本运行的结束时间

echo "TIME:`expr $end - $start`"



运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[iyunv@iZ94yyzmpgvZ /]# . test1.sh
......
[989]   Done                    { sleep 1; echo 'success'$i; }
[990]   Done                    { sleep 1; echo 'success'$i; }
success992
[991]   Done                    { sleep 1; echo 'success'$i; }
[992]   Done                    { sleep 1; echo 'success'$i; }
success993
[993]   Done                    { sleep 1; echo 'success'$i; }
success994
success995
[994]   Done                    { sleep 1; echo 'success'$i; }
success996
[995]   Done                    { sleep 1; echo 'success'$i; }
[996]   Done                    { sleep 1; echo 'success'$i; }
success997
success998
[997]   Done                    { sleep 1; echo 'success'$i; }
success999
[998]   Done                    { sleep 1; echo 'success'$i; }
[999]-  Done                    { sleep 1; echo 'success'$i; }
success1000
[1000]+  Done                    { sleep 1; echo 'success'$i; }
TIME:2




代码解析以及问题:
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(如果管道内容为空,则阻塞)
                                         实验:

QQ截图20160324090335.png


                                        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

实现:      

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#!/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<=10;i++))
do
        echo >&3                   #&3代表引用文件描述符3,这条命令代表往管道里面放入了一个"令牌"
done

for ((i=1;i<=1000;i++))
do
read -u3                           #代表从管道中读取一个令牌
{
        sleep 1  #sleep 1用来模仿执行一条命令需要花费的时间(可以用真实命令来代替)
        echo 'success'$i      
        echo >&3                   #代表我这一次命令执行到最后,把令牌放回管道
}&
done
wait

stop_time=`date +%s`  #定义脚本运行的结束时间

echo "TIME:`expr $stop_time - $start_time`"
exec 3<&-                       #关闭文件描述符的读
exec 3>&-                       #关闭文件描述符的写



运行结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[iyunv@iZ94yyzmpgvZ /]# . test2.sh
success4
success6
success7
success8
success9
success5
......
success935
success941
success942
......
success992
[992]   Done                    { sleep 1; echo 'success'$i; echo 1>&3; }
success993
[993]   Done                    { sleep 1; echo 'success'$i; echo 1>&3; }
success994
[994]   Done                    { sleep 1; echo 'success'$i; echo 1>&3; }
success998
success999
success1000
success997
success995
success996
[995]   Done                    { sleep 1; echo 'success'$i; echo 1>&3; }
TIME:101




代码解析以及问题:
两个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个线程,这样既节省了系统资源又提高了效率


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-194976-1-1.html 上篇帖子: shell脚本自动化--bond 下篇帖子: shell 脚本 变量 获取程序输出结果异常分析
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表