🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
[TOC] 转载:https://www.cnblogs.com/guxuanqing/p/12416130.html 参考: https://cloud.tencent.com/developer/article/1681177 ## 什么是IO多路复用 * IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。**多路是指网络连接,复用指的是同一个线程**,意思是一个线程处理多个网络连接的IO ## 为什么需要IO多路复用 没有IO多路复用机制时,有同步阻塞IO(BIO)、同步非阻塞IO(NIO)两种实现方式 ### 同步阻塞(BIO) * 服务端采用单线程,当accept一个请求后,在recv或send调用阻塞时,将无法accept其他请求(必须等上一个请求处recv或send完),这种模式下`无法处理并发` * 服务器端采用多线程,当accept一个请求后,开启线程进行recv,可以完成并发处理,但随着请求数增加需要增加系统线程, >`大量的线程占用很大的内存空间,并且线程切换会带来很大的开销,10000个线程真正发生读写事件的线程数不会超过20%,每次accept都开一个线程也是一种资源浪费` ### 同步非阻塞(NIO) * 服务器端当accept一个请求后,加入fds集合,每次轮询一遍fds集合recv(非阻塞)数据,没有数据则立即返回错误, >`每次轮询所有fd(包括没有发生读写事件的fd)会很浪费cpu` ### **IO多路复用(现在的做法)** * 服务器端采用单线程通过select/epoll等系统调用获取fd列表,遍历有事件的fd进行accept/recv/send,使其能`支持更多的并发连接请求` * select/epoll 是同步非阻塞的 ## IO多路复用的几种实现方式 ### **select** #### select的优缺点 select的优点:可以在一个线程上同时监听多个连接请求。 select的几大缺点: * 每次调用select,都需要把fd集合(文件描述符)从用户态拷贝到内核态,这个开销在fd很多时会很大 * 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 * select支持的文件描述符数量太小了,默认是1024 ### **poll** poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fd\_set结构,poll支持的文件描述符数量没有限制,其他的都差不多。 poll缺点: * 每次调用poll,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大 * 对socket扫描时是线性扫描,采用轮询的方法,效率较低(高并发时) ### **epoll**  epoll是对select和poll的改进,能避免上述的三个缺点,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll\_create,epoll\_ctl和epoll\_wait,epoll\_create是创建一个epoll句柄;epoll\_ctl是注册要监听的事件类型;epoll\_wait则是等待事件的产生。 >epoll只能工作在linux下 **epoll LT 与 ET模式的区别** * epoll有EPOLLLT和EPOLLET两种触发模式,LT是默认的模式,ET是“高速”模式。 * 1. 水平触发的主要特点是,如果用户在监听epoll事件,当内核有事件的时候,会拷贝给用户态事件,但是如果用户只处理了一次,那么剩下没有处理的会在下一次epoll_wait再次返回该事件。 * 2. 边缘触发,相对跟水平触发相反,当内核有事件到达, 只会通知用户一次,至于用户处理还是不处理,以后将不会再通知---**直到下次再有数据流入之前都不会再提示了**。这样减少了拷贝过程,增加了性能,但是相对来说,如果用户马虎忘记处理,将会产生事件丢的情况。 ` ` #### 解决select第一个缺点   对于第一个缺点,epoll的解决方案在epoll\_ctl函数中。**每次注册新的事件到epoll句柄中时(在epoll\_ctl中指定EPOLL\_CTL\_ADD),会把所有的fd拷贝进内核,而不是在epoll\_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。** ` ` #### 解决select第二个缺点   对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current(当前描述符)轮流加入fd对应的设备等待队列中,而只在epoll\_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll\_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule\_timeout()实现睡一会,判断一会的效果,和select中的实现是类似的)。 ` ` #### 解决select第三个缺点   对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目在linux上可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。 ` ` select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll\_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll\_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。 ` ` select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll\_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。 #### 总结epoll         epoll属于IO多路复用,它只是模拟实现了异步IO的功能。  “真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。 ` ` ### epoll和poll触发时的区别   epoll 因为采用 mmap的机制, 使得 内核socket buffer和 用户空间的 buffer共享, 从而省去了 socket data copy, 这也意味着, 当epoll 回调上层的 callback函数来处理 socket 数据时, 数据已经从内核层 "自动" 到了用户空间, 虽然和 用poll 一样, 用户层的代码还必须要调用 read/write, 但这个函数内部实现所触发的深度不同了。 ` `        用poll时, poll通知用户空间的Appliation时, 数据还在内核空间, 所以Appliation调用 read API 时, 内部会做 copy socket data from kenel space to user space。 ` `        而用 epoll 时, epoll 通知用户空间的Appliation时, 数据已经在用户空间, 所以 Appliation调用 read API 时, 只是读取用户空间的 buffer, 没有 kernal space和 user space的switch了。 ### IOCP(异步io----iocp,epoll)           IOCP全称 IO完成端口。它是一种WIN32的网络I/O模型,既包括了网络连接部分,也负责了部分的I/O操作功能,用于方便我们控制有并发性的网络I/O操作。它有如下特点: 1:它是一个WIN32内核对象,所以无法运行于linux。 2:它自己负责维护了工作线程池,同时也负责了I/O通道的内存池。 3:它自己实现了线程的管理以及I/O请求通知,最小化的做到了线程的上下文切换。 4:它自己实现了线程的优化调度,提高了CPU和内存缓冲的使用率。 >**真正意义上的异步IO严格的来说只有IOCP,但是epoll也模拟实现了异步IO的功能。**       ### IOCP和Epoll之间的异同。 异: 1:IOCP是WINDOWS系统下使用。Epoll是Linux系统下使用。 2:IOCP是IO操作完毕之后,通过Get函数获得一个完成的事件通知。 Epoll是当你希望进行一个IO操作时,向Epoll查询是否可读或者可写,若处于可读或可写状态后,Epoll会通过epoll\_wait进行通知。 3:IOCP封装了异步的消息事件的通知机制,同时封装了部分IO操作。但Epoll仅仅封装了一个异步事件的通知机制,并不负责IO读写操作,但是因为mmap机制,epoll其实已经省去了IO操作的第二部分(将数据从内核缓冲区复制到进程缓冲区)。 4: 基于上面的描述,我们可以知道Epoll不负责IO操作,所以它只告诉你当前可读可写了,并且将协议读写缓冲填充,由用户去读写控制,此时我们可以做出额 外的许多操作。IOCP则直接将IO通道里的读写操作都做完了才通知用户,当IO通道里发生了堵塞等状况我们是无法控制的。 同: 1:它们都是异步的事件驱动的网络模型。 2:它们都可以向底层进行指针数据传递,当返回事件时,除可通知事件类型外,还可以通知事件相关数据(通知到来时IO已经完全完成)。 ## 总结 ### select/poll/epoll之间的区别 | | select | poll | epoll | | --- | --- | --- | --- | | 数据结构 | bitmap | 数组 | 红黑树 | |最大连接数 | 1024 | 无上限(最大可以打开文件的数目) | 无上限(最大可以打开文件的数目)| | fd拷贝 | 每次调用select拷贝 | 每次调用poll拷贝 | fd首次调用epoll\_ctl拷贝,每次调用epoll\_wait不拷贝 | | 工作效率 | 轮询:O(n) | 轮询:O(n) | 回调:O(1) | ### **高频面试题** * 什么是IO多路复用? * nginx/redis 所使用的IO模型是什么? * select、poll、epoll之间的区别 * epoll 水平触发(LT)与 边缘触发(ET)的区别?