p o l l 方 法 的 基 本 概 念
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
IO多路复用select,poll epoll以及区别
要弄清问题先要知道问题的出现原因
由于进程的执行过程是线性的(也就是顺序执行),当我们调用低速系统I-O(read,write,accept等等),进程可能阻塞,此时进程就阻塞在这个调用上,不能执行其他操作.阻塞很正常. 接下来考虑这么一个问题:一个服务器进程和一个客户端进程通信,服务器端read(sockfd1,bud,bufsize),此时客户端进程没有发送数据,那么read(阻塞调用)将阻塞直到客户端调用write(sockfd,but,size) 发来数据. 在一个客户和服务器通信时这没什么问题,当多个客户与服务器通信时,若服务器阻塞于其中一个客户sockfd1,当另一个客户的数据到达套接字sockfd2时,服务器不能处理,仍然阻塞在read(sockfd1.)上;此时问题就出现了,不能及时处理另一个客户的
服务,咋么办?I-O多路复用来解决!I-O多路复用:继续上面的问题,有多个客户连接,sockfd1,sockfd2,sockfd3.sockfdn同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后就调用read读取收到消息的sockfd,然后又循环回select
这样就不会因为阻塞在其中一个上而不能处
理另一个客户的消息
那这样子,在读取socket1的数据时,如果其它socket有数据来,那么也要等到socket1读取完了才能继续读取其它socket的数据吧。
那不是也阻塞住了吗?而且读取到的数据也要开启线程处理吧,那这和多线程
IO有什么区别呢?
3.跟多线程相比较,线程切换需要切换到内核进行线程切换,需要消耗时间和资-源. 而I-O多路复用不需要切换线-进程,效率相对较高,特别是对高并发的应用nginx就是用I-O多路复用,故而性能极佳.但多线程编程逻辑和处理上比I-O多路复用简单.而I-O多路复用处理起来较为复杂.
这些名词比较绕口,理解涵义就好。
一个epoll场景:一个酒吧服务员(一个线程),前面趴了一群醉汉,突然一个吼一声“倒酒”(事件),你小跑过去给他倒一杯,然后随他去吧,突然又一个要倒酒,你又过去倒上,就这样一个服务员服务好多人,有时没人喝酒,服务员处于空闲状态,可以干点别的玩玩手机。
至于epoll与select,poll的区别在于后两者的场景中醉汉不说话,你要挨个问要不要酒,没时间玩手机了。
io多路复用大概就是指这几个醉汉共用一个服务员。
IO 多路复用是5种I-O模型中的第3种,对各种模型讲个故事,描述下区别:
故事情节为:老李去买火车票,三天后买到一张退票。
参演人员(老李,黄牛,售票员,快递员),往返车站耗费1小时。
1.阻塞I-O模型
老李去火车站买票,排队三天买到一张退票。
耗费:在车站吃喝拉撒睡 3天,其他事一件没干。
2.非阻塞I-O模型
老李去火车站买票,隔12小时去火车站问有没有退票,三天后买到一张票。
耗费:往返车站6次,路上6小时,其他时间做了好多事。
3.I-O复用模型
1.select-poll
老李去火车站买票,委托黄牛,然后每隔6小时电话黄牛询问,黄牛三天内买到票,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,打电话17次老李去火车站买票,委托黄牛,黄牛买到后即通知老李去领,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,黄牛手续费100元,无需打电话
4.信号驱动I-O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李,然后老李去火车站交钱领票。
耗费:往返车站2次,路上2小时,免黄牛费100元,无需打电话
5.异步I-O模型
老李去火车站买票,给售票员留下电话,有票后,售票员电话通知老李并快递送票上门。
耗费:往返车站1次,路上1小时,免黄牛费100元,无需打电话1同2的区别是:自己轮询
2同3的区别是:委托黄牛
3同4的区别是:电话代替黄牛
4同5的区别是:电话通知是自取还是送票上门
这个还是很好说清楚的。
假设你是一个机场的空管,你需要管理到你机场的所有的航线,包括进港,出港,有些航班需要放到停机坪等待,有些航班需要去登机口接乘客。
你会怎么做?
最简单的做法,就是你去招一大批空管员,然后每人盯一架飞机,从进港,接客,排位,出港,航线监控,直至交接给下一个空港,全程监控。
那么问题就来了:
很快你就发现空管塔里面聚集起来一大票的空管员,交通稍微繁忙一点,新的空管员就已经挤不进来了。
空管员之间需要协调,屋子里面就1, 2个人的时候还好,几十号人以后,基本上就成菜市场了。
空管员经常需要更新一些公用的东西,比如起飞显示屏,比如下一个小时后的出港排期,最后你会很惊奇的发现,每个人的时间最后都花在了抢这些资-源上。
现实上我们的空管同时管几十架飞机稀松平常的事情,他们怎么做的呢?
他们用这个东西
这个东西叫flight progress strip. 每一个块代表一个航班,不同的槽代表不同的状态,然后一个空管员可以管理一组这样的块(一组航班),而他的工作,就是在航班信息有新的更新的时候,把对应的块放到不同的槽子里面。
这个东西现在还没有淘汰哦,只是变成电子的了而已。
是不是觉得一下子效率高了很多,一个空管塔里可以调度的航线可以是前一种方法的几倍到几十倍。
如果你把每一个航线当成一个Sock(I-O 流), 空管当成你的服务端Sock管理代码的话.
第一种方法就是最传统的多进程并发模型 (每进来一个新的I-O流会分配一个新的进程管理。
)第二种方法就是I-O多路复用 (单个线程,通过记录跟踪每个I-O流(sock)的状态,来同时管理多个I-O流。
) 其实“I-O多路复用”这个坑爹翻译可能是这个概念在中文里面如此难理解的原因。
所谓的I-O多路复用在英文中其实叫 I-O multiplexing. 如果你搜索multiplexing啥意思,基本上都会出这个图:
于是大部分人都直接联想到"一根网线,多个sock复用" 这个概念,包括上面的几个回答,其实不管你用多进程还是I-O多路复用,网线都只有一根好伐。
多个Sock复用一根网线这个功能是在内核+驱动层实现的。
重要的事情再说一遍: I-O multiplexing 这里面的 multiplexing 指的其实是在单个线程通过记录跟踪每一个Sock(I-O流)的状态(对应空管塔里面的Fight progress strip槽)来同时管理多个I-O流. 发明它的原因,是尽量多的提高服务器的吞吐能力。
是不是听起来好拗口,看个图就懂了.
在同一个线程里面,通过拨开关的方式,来同时传输多个I-O流, (学过EE的人现在可以站出来义正严辞说这个叫“时分复用”了)。
------------------------------------------
了解这个基本的概念以后,其他的就很好解释了。
select, poll, epoll 都是I-O多路复用的具体的实现,之所以有这三个鬼存在,其实是他们出现是有先后顺序的。
I-O多路复用这个概念被提出来以后, select是第一个实现 (1983 左右在BSD里面实现的)。
select 被实现以后,很快就暴露出了很多问题。
select 会修改传入的参数数组,这个对于一个需要调用很多次的函数,是非常不友好的。
select 如果任何一个sock(I-O stream)出现了数据,select 仅仅会返回,但是并不会告诉你是那个sock上有数据,于是你只能自己一个一个的找,10几个sock可能还好,要是几万的sock每次都找一遍,这个无谓的开销就颇有海天盛筵的豪气了。
select 不是线程安全的,如果你把一个sock加入到select, 然后突然另外一个线程发现,尼玛,这个sock不用,要收回。
对不起,这个select 不支持的,如果你丧心病狂的竟然关掉这个sock, select的标准行为是。
呃。
不可预测的,这个可是写在文档中的哦.
“If a file descriptor being monitored by select() is closed in another thread, the result is unspecified”
霸不霸气
于是14年以后(1997年)一帮人又实现了poll, poll 修复了select 的很多问题,比如
poll 从设计上来说,不再修改传入数组,不过这个要看你的平台
了,所以行走江湖,还是小心为妙。
但是poll仍然不是线程安全的,这就意味着,不管服务器有多强悍,你也只能在一个线程里面处理一组I-O流。
你当然可以那多进程来配合了,不过然后你就有了多进程的各种问题。
于是5年以后, 在2002, 大神 Davide Libenzi 实现了epoll.
epoll 可以说是I-O 多路复用最新的一个实现,epoll 修复了poll 和select绝大部分问题, 比如:
epoll 现在是线程安全的。
epoll 现在不仅告诉你sock组里面数据,还会告诉你具体哪个sock有数据,你不用自己去找了。
贴一张霸气的图,看看当年神一样的性能(测试代码都是死链了,如果有人可以刨坟找出来,可以研究下细节怎么测的).
可是epoll 有个致命的缺点。
只有linux支持。
比如BSD上面对应的实现是kqueue。
其实有些国内知名厂商把epoll从安卓里面裁掉这种脑残的事情我会主动告诉你嘛。
什么,你说没人用安卓做服务器,尼玛你是看不起p2p软件了啦。
而ngnix 的设计原则里面,它会使用目标平台上面最高效的I-O多路复用模型咯,所以才会有这个设置。
一般情况下,如果可能的话,尽量都用epoll-kqueue吧。
详细的在这里:Connection processing methods
PS: 上面所有这些比较分析,都建立在大并发下面,如果你的并发数
太少,用哪个,其实都没有区别。
如果像是在欧朋数据中心里面的转码服务器那种动不动就是几万几十万的并发,不用epoll我可以直接去撞墙了。
从根结点开始沿着leftChild到最下角的结点,将指针依次压入栈中,直到该结点的leftChild指针为NULL。
访问它的数据后,再遍历该结点的右子树。
此时该结点为栈中推出的指针。
TaskTracker 100% of a CPU, and separate disks to read and write data.
static void do_write(int epollfd,int fd,char *buf) {
其中ArrayList类的底层是采用动态数组实现的,因此增删不方便,访问元素方便;
java -Xmx12g -XX:+UseParallelGC Producer
client_socket = socket(AF_INET, SOCK_STREAM, 0);
epoll不需要每次都从user space 将fd set复制到内核kernel space 上面我们实现了二叉堆,基于二叉堆可以非常方便的实现优先队列。
着重讲fdarray数组,因为这是它和select()函数主要的不同的地方:pollfd的结构如下:
申请字符设备编号,设备,并初始化--uio_major_init。