请教关于多线程epoll

请教关于多线程epoll,第1张

当一个节点和多个节点建立连接时,如何高效的处理多个连接的数据,下面具体分析两者的区别。1.select函数函数原型:intselect(intnfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,structtimeval*timeout)参数介绍:(1)nfds--fdset集合中最大描述符值加1(2)fdset--一个位数组,其大小限制为_FD_SETSIZE(1024)位数组的每一位代表的是其对应的描述符是否需要被检查。(3)readfds--读事件文件描述符数组(4)writefds--写事件文件描述符数组(5)exceptfds--错误事件文件描述符数组(6)timeout--超时哗庆事件,该结构被内核修改,其值为超时剩余时间。对应内核:select对应于内核中的sys_select调用,sys_select首先将第二三四个参数指向的fd_set拷贝到内核,然后对每个被SET的描述符调用进行poll,并记录在临时结果中(fdset),如兄大果有事件发生,select会将临时结果写到用户空间并返回;当轮询一遍乱尘握后没有任何事件发生时,如果指定了超时时间,则select会睡眠到超时,睡眠结束后再进行一次轮询,并将临时结果写到用户空间,然后返2.select/poll特点传统的select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。poll的执行分三部分:(1).将用户传入的pollfd数组拷贝到内核空间,因为拷贝 *** 作和数组长度相关,时间上这是一个O(n) *** 作(2).查询每个文件描述符对应设备的状态,如果该设备尚未就绪,则在该设备的等待队列中加入一项并继续查询下一设备的状态。查询完所有设备后如果没有一个设备就绪,这时则需要挂起当前进程等待,直到设备就绪或者超时。设备就绪后进程被通知继续运行,这时再次遍历所有设备,以查找就绪设备。这一步因为两次遍历所有设备,时间复杂度也是O(n),这里面不包括等待时间(3).将获得的数据传送到用户空间并执行释放内存和剥离等待队列等善后工作,向用户空间拷贝数据与剥离等待队列等 *** 作的的时间复杂度同样是O(n)。3.epoll机制Linux2.6内核完全支持epoll。epoll的IO效率不随FD数目增加而线性下降。要使用epoll只需要这三个系统调用:epoll_create(2),epoll_ctl(2),epoll_wait(2)epoll用到的所有函数都是在头文件sys/epoll.h中声明的,内核实现中epoll是根据每个fd上面的callback函数实现的。只有"活跃"的socket才会主动的去调用callback函数,其他idle状态socket则不会。如果所有的socket基本上都是活跃的---比如一个高速LAN环境,过多使用epoll,效率相比还有稍微的下降。但是一旦使用idleconnections模拟WAN环境,epoll的效率就远在select/poll之上了。3.1所用到的函数:(1)、intepoll_create(intsize)该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围(2)、intepoll_ctl(intepfd,intop,intfd,structepoll_event*event)用于控制某个文件描述符上的事件,可以注册事件,修改事件,删除事件。如果调用成功返回0,不成功返回-1intepoll_ctl{intepfd,//由epoll_create生成的epoll专用的文件描述符intop,//要进行的 *** 作例如注册事件,可能的取值EPOLL_CTL_ADD注册、//EPOLL_CTL_MOD修改、EPOLL_CTL_DEL删除intfd,//关联的文件描述符structepoll_event*event//指向epoll_event的指针}(3)、intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout)用于轮询I/O事件的发生,返回发生事件数intepoll_wait{intepfd,//由epoll_create生成的epoll专用的文件描述符structepoll_event*events,//用于回传代处理事件的数组intmaxevents,//每次能处理的事件数inttimeout//等待I/O事件发生的超时值//为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件//为任意正整数的时候表示等这么长的时间,如果一直没有事件//一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率//如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率}epoll是为处理大批量句柄而作了改进的poll。4.epoll的优点:支持一个进程打开大数目的socket描述符(FD)select最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候可以:(1)可以修改这个宏然后重新编译内核,不过资料也同时指出,这样也会带来网络效率的下降(2)可以选择多进程的解决方案,不过虽然linux上创建进程的代价比较下,但是仍旧是不可忽视的,所以也不是很完美的方案epoll没有这样的限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,具体数组可以查看cat/proc/sys/fs/file-max查看,这个数目和系统内存关系很大。IO效率不随FD数目增加而线性下降传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。epoll不存在这个问题,它只会对“活跃”的socket进行 *** 作。这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idleconnections模拟WAN环境,epoll的效率就远在select/poll之上了。使用mmap加速内核与用户空间的消息传递这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工mmap这一步的。内核微调这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小---通过echoXXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。

epoll跟select都能提供多路I/O复用的解决方案。在现在的Linux内核里有都能够支持,其中epoll是Linux所特有,而select则应该是POSIX所规定,一般 *** 作系统均有实现。

网上现在关于这两者不同的介绍已经到处都是了。我这里也不能多说出什么东西,只是记录下我看了实现代码之后的一些总结。

两者的使用场景一般是通过一个入口能够同时监控多路I/O。一般使用的接口,

epool就是

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

select为:

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

通过上述两个函数能够将调用线程阻塞,线程变为可执行条件有两种情况:

无任何事件发生,超时时间已过

在所控制的I/O有事件到来

epoll_wait函数中看不到相关的监控信息,因为是通过epoll_ctl已经加入,而select之间在函数调用中由(fd_set *readfds, fd_set *writefds, fd_set *exceptfds)传入。epoll_wait饭互结果通过events返回,而select的传入参数也是传出参数。两者传出参数均表示发生事件的对应I/O标识。

两种方式的区别主要体现在以下几个方面:

select所能控制的I/O数有限,这主要是因为fd_set数据结构是一个有大小的,相当与一个定长所数组。

select每次都需要重新设置所要监控的fd_set(因为调用信裤之后会改变其内容),这增加了程序开销。

select的性能要比epoll差,具体原因会在后续内容中详细说明。

嗯,说道这个为什么select要差,那就要从这个select API说起了。这个传进去一个数组,内部实现也不知道那个有哪个没有,所以要遍历一遍。假设说我只监控一个文件描述符,但是他是1000。那么select需要遍历前999个之后再来poll这个1000的文件描述符,而epoll则不需要,因为在之前epoll_ctl的调用过程中,已经维护了一个队列,所以直接等待事件到来就滑卜简可以了。

Linux中select此段相关代码为:

/* 遍历所有传入的fd_set */

for (i = 0i <n++rinp, ++routp, ++rexp) {

unsigned long in, out, ex, all_bits, bit = 1, mask, j

unsigned long res_in = 0, res_out = 0, res_ex = 0

const struct file_operations *f_op = NULL

struct file *file = NULL

in = *inp++out = *outp++ex = *exp++

all_bits = in | out | ex

/* 此处跳无需监控的fd, 白白的浪费时间啊…… */

if (all_bits == 0) {

i += __NFDBITS

continue

}

/* 后续进行一些相关 *** 作 */

}

而epoll则无需进行此类 *** 作,直接检测内部维护的一个就绪队列,如果队列有内容,说明有I/O就绪,那么直接赋值返回内容,弊没成功返回,如果没有成功,那么睡眠,等待就绪队列非空。

通过这个两者的比较,其实两者的差距啊,大部分是因为这个API设计所决定的,select就设计成这样一个API,内部再怎么优化也只能是这么个烂样子,而epoll这样维护与等待分离,灵活多变,最后也就带来了相对的高性能,以及可扩展性。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/12133181.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-21
下一篇 2023-05-21

发表评论

登录后才能评论

评论列表(0条)

保存