导航:首页 > 编程语言 > epoll多线程编程

epoll多线程编程

发布时间:2022-12-06 11:49:06

Ⅰ epoll和多线程哪个好

多线程。
1、面向多核的服务器编程时,多线程强于epoll,因为对于每个多进程来说,资源是独立的,切换core的时候无需考虑上下文。
2、每个资源共享时以及在core切换的时候,多线程更好,多线程能够直接进行共享,而epoll资源必须从一个core复制到另一个core才能继续运算。

Ⅱ 关于linux下的select/epoll

select这个系统调用的原型如下

第一个参数nfds用来告诉内核 要扫描的socket fd的数量+1 ,select系统调用最大接收的数量是1024,但是如果每次都去扫描1024,实际上的数量并不多,则效率太低,这里可以指定需要扫描的数量。 最大数量为1024,如果需要修改这个数量,则需要重新编译Linux内核源码
第2、3、4个参数分别是readfds、writefds、exceptfds,传递的参数应该是fd_set 类型的引用,内核会检测每个socket的fd, 如果没有读事件,就将对应的fd从第二个参数传入的fd_set中移除,如果没有写事件,就将对应的fd从第二个参数的fd_set中移除,如果没有异常事件,就将对应的fd从第三个参数的fd_set中移除 。这里我们应该 要将实际的readfds、writefds、exceptfds拷贝一份副本传进去,而不是传入原引用,因为如果传递的是原引用,某些socket可能就已经丢失
最后一个参数是等待时间, 传入0表示非阻塞,传入>0表示等待一定时间,传入NULL表示阻塞,直到等到某个socket就绪

FD_ZERO()这个函数将fd_set中的所有bit清0,一般用来进行初始化等。
FD_CLR()这个函数用来将bitmap(fd_set )中的某个bit清0,在客户端异常退出时就会用到这个函数,将fd从fd_set中删除。
FD_ISSET()用来判断某个bit是否被置1了,也就是判断某个fd是否在fd_set中。
FD_SET()这个函数用来将某个fd加入fd_set中,当客户端新加入连接时就会使用到这个函数。

epoll_create系统调用用来创建epfd,会在开辟一块内存空间(epoll的结构空间)。size为epoll上能关注的最大描述符数,不够会进行扩展,size只要>0就行,早期的设计size是固定大小,但是现在size参数没什么用,会自动扩展。
返回值是epfd,如果为-1则说明创建epoll对象失败

第一个参数epfd传入的就是epoll_create返回的epfd。
第二个参数传入对应操作的宏,包括 增删改(EPOLL_CTL_ADD、EPOLL_CTL_DEL、EPOLL_CTL_MOD)
第三个参数传入的是 需要增删改的socket的fd
第四个参数传入的是 需要操作的fd的哪些事件 ,具体的事件可以看后续。
返回值是一个int类型,如果为-1则说明操作失败

第一个参数是epfd,也就是epoll_create的返回值。
第二个参数是一个epoll_event类型的指针,也就是传入的是一个数组指针。 内核会将就绪的socket的事件拷贝到这个数组中,用户可以根据这个数组拿到事件和消息等
第三个参数是maxevents,传入的是 第二个参数的数组的容量
第四个参数是timeout, 如果设为-1一直阻塞直到有就绪数据为止,如果设为0立即返回,如果>0那么阻塞一段时间
返回值是一个int类型,也就是就绪的socket的事件的数量(内核拷贝给用户的events的元素的数量),通过这个数量可以进行遍历处理每个事件

一般需要传入 ev.data.fd 和 ev.events ,也就是fd和需要监控的fd的事件。事件如果需要传入多个,可以通过按位与来连接,比如需要监控读写事件,只需要像如下这样操作即可: ev.events=EPOLLIN | EPOLLOUT 。

LT(水平触发), 默认 的工作模式, 事件就绪后用户可以选择处理和不处理,如果用户不处理,内核会对这部分数据进行维护,那么下次调用epoll_wait()时仍旧会打包出来
ET(边缘触发),事件就绪之后, 用户必须进行处理 ,因为内核把事件打包出来之后就把对应的就绪事件给清掉了, 如果不处理那么就绪事件就没了 。ET可以减少epoll事件被重复触发的次数,效率比LT高。
如果需要设置为边缘触发只需要设置事件为类似 ev.events=EPOLLIN | EPOLLET 即可

select/poll/epoll是nio多路复用技术, 传统的bio无法实现C10K/C100K ,也就是无法满足1w/10w的并发量,在这么高的并发量下,在进行上下文切换就很容易将服务器的负载拉飞。

1.将fd_set从用户态拷贝到内核态
2.根据fd_set扫描内存中的socket的fd的状态,时间复杂度为O(n)
3.检查fd_set,如果有已经就绪的socket,就给对应的socket的fd打标记,那么就return 就绪socket的数量并唤醒当前线程,如果没有就绪的socket就继续阻塞当前线程直到有socket就绪才将当前线程唤醒。
4.如果想要获取当前已经就绪的socket列表,则还需要进行一次系统调用,使用O(n)的时间去扫描socket的fd列表,将已经打上标记的socket的fd返回。

CPU在同一个时刻只能执行一个程序,通过RR时间片轮转去切换执行各个程序。没有被挂起的进程(线程)则在工作队列中排队等待CPU的执行,将进程(线程)从工作队列中移除就是挂起,反映到Java层面的就是线程的阻塞。

什么是中断?当我们使用键盘、鼠标等IO设备的时候,会给主板一个电流信号,这个电流信号就给CPU一个中断信号,CPU执行完当前的指令便会保存现场,然后执行键盘/鼠标等设备的中断程序,让中断程序获取CPU的使用权,在中断程序后又将现场恢复,继续执行之前的进程。

如果第一次没检测到就绪的socket,就要将其进程(线程)从工作队列中移除,并加入到socket的等待队列中。

socket包含读缓冲区+写缓冲区+等待队列(放线程或eventpoll对象)

当从客户端往服务器端发送数据时,使用TCP/IP协议将通过物理链路、网线发给服务器的网卡设备,网卡的DMA设备将接收到的的数据写入到内存中的一块区域(网卡缓冲区),然后会给CPU发出一个中断信号,CPU执行完当前指令则会保存现场,然后网卡的中断程序就获得了CPU的使用权,然后CPU便开始执行网卡的中断程序,将内存中的缓存区中的数据包拿出,判断端口号便可以判断它是哪个socket的数据,将数据包写入对应的socket的读(输入)缓冲区,去检查对应的socket的等待队列有没有等待着的进程(线程),如果有就将该线程(进程)从socket的等待队列中移除,将其加入工作队列,这时候该进程(线程)就再次拥有了CPU的使用权限,到这里中断程序就结束了。

之后这个进程(线程)就执行select函数再次去检查fd_set就能发现有socket缓冲区中有数据了,就将该socket的fd打标记,这个时候select函数就执行完了,这时候就会给上层返回一个int类型的数值,表示已经就绪的socket的数量或者是发生了错误。这个时候就再进行内核态到用户态的切换,对已经打标记的socket的fd进行处理。

将原本1024bit长度的bitmap(fd_set)换成了数组的方式传入 ,可以 解决原本1024个不够用的情况 ,因为传入的是数组,长度可以不止是1024了,因此socket数量可以更多,在Kernel底层会将数组转换成链表。

在十多年前,linux2.6之前,不支持epoll,当时可能会选择用Windows/Unix用作服务器,而不会去选择Linux,因为select/poll会随着并发量的上升,性能变得越来越低,每次都得检查所有的Socket列表。

1.select/poll每次调用都必须根据提供所有的socket集合,然后就 会涉及到将这个集合从用户空间拷贝到内核空间,在这个过程中很耗费性能 。但是 其实每次的socket集合的变化也许并不大,也许就1-2个socket ,但是它会全部进行拷贝,全部进行遍历一一判断是否就绪。

2.select/poll的返回类型是int,只能代表当前的就绪的socket的数量/发生了错误, 如果还需要知道是哪些socket就绪了,则还需要再次使用系统调用去检查哪些socket是就绪的,又是一次O(n)的操作,很耗费性能

1.epoll在Kernel内核中存储了对应的数据结构(eventpoll)。我们可以 使用epoll_create()这个系统调用去创建一个eventpoll对象 ,并返回eventpoll的对象id(epfd),eventpoll对象主要包括三个部分:需要处理的正在监听的socket_fd列表(红黑树结构)、socket就绪列表以及等待队列(线程)。

2.我们可以使用epoll_ctl()这个系统调用对socket_fd列表进行CRUD操作,因为可能频繁地进行CRUD,因此 socket_fd使用的是红黑树的结构 ,让其效率能更高。epoll_ctl()传递的参数主要是epfd(eventpoll对象id)。

3.epoll_wait()这个系统调用默认会 将当前进程(线程)阻塞,加入到eventpoll对象的等待队列中,直到socket就绪列表中有socket,才会将该进程(线程)重新加入工作队列 ,并返回就绪队列中的socket的数量。

socket包含读缓冲区、写缓冲区和等待队列。当使用epoll_ctl()系统调用将socket新加入socket_fd列表时,就会将eventpoll对象引用加到socket的等待队列中, 当网卡的中断程序发现socket的等待队列中不是一个进程(线程),而是一个eventpoll对象的引用,就将socket引用追加到eventpoll对象的就绪列表的尾部 。而eventpoll对象中的等待队列存放的就是调用了epoll_wait()的进程(线程),网卡的中断程序执行会将等待队列中的进程(线程)重新加入工作队列,让其拥有占用CPU执行的资格。epoll_wait()的返回值是int类型,返回的是就绪的socket的数量/发生错误,-1表示发生错误。

epoll的参数有传入一个epoll_event的数组指针(作为输出参数),在调用epoll_wait()返回的同时,Kernel内核还会将就绪的socket列表添加到epoll_event类型的数组当中。

Ⅲ 多线程处理epoll的常用方式

先说epoll 官方文档的建议,在ET模式下,如果用多线程epoll_wait 同一个epoll-fd,那么当其监听fd产生了事件,此时理论上所有的线程都会收到通知,这将导致群惊,因此epoll只会唤醒一个线程来处理!

但是,此时如果fd又触发了新的事件,那么就会唤醒新的线程!这将会导致多个线程操作同一个fd,这不是推荐的方式,可能导致线程安全问题!

解决方案是使用EPOLLNESHOT标志,即在一次wait返回后禁止fd再产生事件,并在处理完成后使用epoll_ctl的MOD操作重新开启。

但是在webServer的实现里面,一般是这样的:

一个线程负责监听TCP链接,当收到用户的TCP链接后,创建client-fd ,然后从线程池中挑选一个线程,将client -fd添加到工作线程的epoll中,当client-fd收到HTTP请求,由工作线程处理之。

换句话说,每个线程都处理自己的fd,从而完全避免多线程安全问题,当然了上述操作主要是为了高并发,它有个熟悉的名字,Reactor模型。

Pistache就是上述工作模式。

Ⅳ 请教关于多线程epoll

当一个节点和多个节点建立连接时,如何高效的处理多个连接的数据,下面具体分析两者的区别。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网卡驱动架构。

Ⅳ linux并发服务器中epoll+多线程分别怎么理

某个线程处理某个特定事件吧
通过epoll检测一些事件,事件触发时,创建一个线程来专职处理这个事件

Ⅵ poll,select,epoll是多线程实现的吗

在Linux Socket服务器短编程时,为了处理大量客户的连接请求,需要使用非阻塞I/O和复用,select、poll 和epoll是Linux API提供的I/O复用方式,自从Linux 2.6中加入了epoll之后

Ⅶ Linux开发,使用多线程还是用IO复用select/epoll

多线程和用select/epoll是没有关联的,在select和epoll模型里也可以使用多线程进行io处理,select/epoll
的出现是为了解决一个线程对应一个请求时阻塞线程的问题,基于epoll的事件模型,解决了线程的阻塞问题即一个线程可以为多个请求服务

Ⅷ linux手册翻译——epoll(7)


epoll — I/O 事件通知机制


epoll API与poll具有相同功能:监视多个文件描述符,以查看这些文件描述符中任何一个上可以进行特定的I/O操作,如是否可读/可写。epoll API可以使用edge-triggered和level-triggered两种接口,并且可以高性能的同时监视大量的fd,这是对epoll相对鱼poll的核心优势。

epoll的核心概念是epoll instance,这是一种内核数据结构,从用户空间角度看,可以视为一个包含两种列表的容器:

提供以下3个系统调用来创建和管理epoll instance:

两种触发模式:level_triggered (LT)和 edge_triggered(ET)
假设发生如下场景:

如果使用ET触发,那么步骤5就会阻塞挂起,这是因为对于ET模式而言,只有当缓冲区数据发生变化时才会触发事件(对于读,“变化”指新数据到达)。而对于LT而言,只要缓冲区中存在数据,就会一直触发。

使用ET时应使用非阻塞的fd (即无法读写时返回EAGIN,而非阻塞),以避免task阻塞导教其他fd无法监控。
合理使用ET模式步骤:
1)修改fd为非阻塞(non-blocking)
2)在read或write操作返回EAGIN后再执行wait等待事件。
为何ET需要非阻塞呢?因为ET模式下要循环多次read,并通过阻塞(即是否返回EAGIN)来确定数据是否全部读完。第一次执行read是不可能阻塞的。

若使用LT模式(默认情况下,使用ET模式),则可以将epoll看作是一个快速的poll,可以在任何地方使用epoll(LT)替换poll,因为他们的语义完全相同。
即使采用ET模式,在多线程的情况依然会导致产生多个事件(对于同一被监控的fd),这将导致多个线程操作同一fd,可以使用EPOLLNESHOT标志避免,即在一次wait返回后禁止fd再产生事件,并在处理完成后使用epoll_ctl的MOD操作重新开启。
在多进程或多线程中,epoll_fd是共享的,这将导致所有线程都会知道事情的发生,但是epoll仅会唤醒一个线程,以规避“群惊”现象。

If the system is in autosleep mode via /sys/power/autosleep and an event happens which wakes the device from sleep, the device driver will keep the device awake only until that event is queued. To keep the device awake until the event has been processed, it is necessary to use the epoll_ctl(2) EPOLLWAKEUP flag.

When the EPOLLWAKEUP flag is set in the events field for a struct epoll_event, the system will be kept awake from the moment the event is queued, through the epoll_wait(2) call which returns the event until the subsequent epoll_wait(2) call. If the event should keep the system awake beyond that time, then a separate wake_lock should be taken before the second epoll_wait(2) call.

以下接口可用于限制 epoll 消耗的内核内存用量:

虽然 epoll 在用作级别触发接口时具有与 poll(2) 相同的语义,但边缘触发的用法需要更多说明以避免应用程序事件循环中的阻塞。
在下面例子中,listener 是一个非阻塞套接字,在它上面调用了 listen(2)。 函数 do_use_fd() 使用新的就绪文件描述符,直到 read(2) 或 write(2) 返回 EAGAIN。 事件驱动的状态机应用程序应该在收到 EAGAIN 后记录其当前状态,以便在下一次调用 do_use_fd() 时,它将继续从之前停止的位置read (2) 或write (2)。

当使用ET模式时,出于性能原因,可以通过EPOLL_CTL_ADD调用 epoll_ctl(2)指定 (EPOLLIN|EPOLLOUT)添加一次文件描述符。 避免使用 EPOLL_CTL_MOD 调用 epoll_ctl(2)在 EPOLLIN 和 EPOLLOUT 之间连续切换。


The epoll API is Linux-specific. Some other systems provide similar mechanisms, for example, FreeBSD has kqueue, and Solaris has /dev/poll.


通过 epoll 文件描述符监视的文件描述符集可以通过进程的 /proc/[pid]/fdinfo 目录中的 epoll 文件描述符条目查看。 有关更多详细信息,请参阅 proc(5)。

kcmp(2) KCMP_EPOLL_TFD 操作可用于测试文件描述符是否存在于 epoll 实例中。

阅读全文

与epoll多线程编程相关的资料

热点内容
服务器怎么用不会断电 浏览:301
主从服务器有什么用 浏览:213
jstlpdf 浏览:14
安卓原神在哪个app下载 浏览:808
单片机编程技术什么意思 浏览:104
e点课堂源码 浏览:45
免费打击垫app哪个好 浏览:532
程序员必装的6款软件 浏览:750
基于单片机的遥控器设计 浏览:521
安卓如何取消圆图标 浏览:11
收件服务器怎么样 浏览:48
建筑设计规范pdf 浏览:98
如何合并两个pdf 浏览:174
刷机包必须要解压的单词 浏览:483
android课表实现 浏览:864
头条app在哪里能看见有什么活动 浏览:511
冰柜压缩机电容80欧 浏览:609
安卓各个版本图标什么样 浏览:152
无锡哪里有制作手机app 浏览:538
php字符串转json数组 浏览:6