用什么软件上传网站,重庆建设工程信息网查询系统,wordpress添加发布视频,沈阳做网站黑酷科技转载自 epoll 浅析以及 nio 中的 Selector首先介绍下epoll的基本原理#xff0c;网上有很多版本#xff0c;这里选择一个个人觉得相对清晰的讲解#xff08;详情见reference#xff09;#xff1a;首先我们来定义流的概念#xff0c;一个流可以是文件#xff0c;socket网上有很多版本这里选择一个个人觉得相对清晰的讲解详情见reference首先我们来定义流的概念一个流可以是文件socketpipe等等可以进行I/O操作的内核对象。不管是文件还是套接字还是管道我们都可以把他们看作流。之后我们来讨论I/O的操作通过read我们可以从流中读入数据通过write我们可以往流写入数据。现在假定一个情形我们需要从流中读数据但是流中还没有数据典型的例子为客户端要从socket读如数据但是服务器还没有把数据传回来这时候该怎么办阻塞阻塞是个什么概念呢比如某个时候你在等快递但是你不知道快递什么时候过来而且你没有别的事可以干或者说接下来的事要等快递来了才能做那么你可以去睡觉了因为你知道快递把货送来时一定会给你打个电话假定一定能叫醒你。非阻塞忙轮询接着上面等快递的例子如果用忙轮询的方法那么你需要知道快递员的手机号然后每分钟给他挂个电话“你到了没”很明显一般人不会用第二种做法不仅显很无脑浪费话费不说还占用了快递员大量的时间。大部分程序也不会用第二种做法因为第一种方法经济而简单经济是指消耗很少的CPU时间如果线程睡眠了就掉出了系统的调度队列暂时不会去瓜分CPU宝贵的时间片了。为了了解阻塞是如何进行的我们来讨论缓冲区以及内核缓冲区最终把I/O事件解释清楚。缓冲区的引入是为了减少频繁I/O操作而引起频繁的系统调用你知道它很慢的当你操作一个流时更多的是以缓冲区为单位进行操作这是相对于用户空间而言。对于内核来说也需要缓冲区。假设有一个管道进程A为管道的写入方为管道的读出方。假设一开始内核缓冲区是空的B作为读出方被阻塞着。然后首先A往管道写入这时候内核缓冲区由空的状态变到非空状态内核就会产生一个事件告诉该醒来了这个事件姑且称之为“缓冲区非空”。但是“缓冲区非空”事件通知B后B却还没有读出数据且内核许诺了不能把写入管道中的数据丢掉这个时候写入的数据会滞留在内核缓冲区中如果内核也缓冲区满了B仍未开始读数据最终内核缓冲区会被填满这个时候会产生一个I/O事件告诉进程A你该等等阻塞了我们把这个事件定义为“缓冲区满”。假设后来终于开始读数据了于是内核的缓冲区空了出来这时候内核会告诉A内核缓冲区有空位了你可以从长眠中醒来了继续写数据了我们把这个事件叫做“缓冲区非满”也许事件Y1已经通知了A但是A也没有数据写入了而继续读出数据知道内核缓冲区空了。这个时候内核就告诉B你需要阻塞了我们把这个时间定为“缓冲区空”。这四个情形涵盖了四个I/O事件缓冲区满缓冲区空缓冲区非空缓冲区非满注都是说的内核缓冲区且这四个术语都是我生造的仅为解释其原理而造。这四个I/O事件是进行阻塞同步的根本。如果不能理解“同步”是什么概念请学习操作系统的锁信号量条件变量等任务同步方面的相关知识。然后我们来说说阻塞I/O的缺点。但是阻塞I/O模式下一个线程只能处理一个流的I/O事件。如果想要同时处理多个流要么多进程(fork)要么多线程(pthread_create)很不幸这两种方法效率都不高。于是再来考虑非阻塞忙轮询的I/O方式我们发现我们可以同时处理多个流了把一个流从阻塞模式切换到非阻塞模式再此不予讨论123456whiletrue { fori in stream[]; { ifi has data read until unavailable }}我们只要不停的把所有流从头到尾问一遍又从头开始。这样就可以处理多个流了但这样的做法显然不好因为如果所有的流都没有数据那么只会白白浪费CPU。这里要补充一点阻塞模式下内核对于I/O事件的处理是阻塞或者唤醒而非阻塞模式下则把I/O事件交给其他对象后文介绍的select以及epoll处理甚至直接忽略。为了避免CPU空转可以引进了一个代理一开始有一位叫做select的代理后来又有一位叫做poll的代理不过两者的本质是一样的。这个代理比较厉害可以同时观察许多流的I/O事件在空闲的时候会把当前线程阻塞掉当有一个或多个流有I/O事件时就从阻塞态中醒来于是我们的程序就会轮询一遍所有的流于是我们可以把“忙”字去掉了。代码长这样:1234567whiletrue { select(streams[]) fori in streams[] { ifi has data read until unavailable }}于是如果没有I/O事件产生我们的程序就会阻塞在select处。但是依然有个问题我们从select那里仅仅知道了有I/O事件发生了但却并不知道是那几个流可能有一个多个甚至全部我们只能无差别轮询所有流找出能读出数据或者写入数据的流对他们进行操作。但是使用select我们有O(n)的无差别轮询复杂度同时处理的流越多没一次无差别轮询时间就越长。再次说了这么多终于能好好解释epoll了epoll可以理解为event poll不同于忙轮询和无差别轮询epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。复杂度降低到了O(1)在讨论epoll的实现细节之前先把epoll的相关操作列出epoll_create 创建一个epoll对象一般epollfd epoll_create()epoll_ctl epoll_add/epoll_del的合体往epoll对象中增加/删除某一个流的某一个事件比如epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件即有数据流入epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件即流可以被写入epoll_wait(epollfd,…)等待直到注册的事件发生注当对一个非阻塞流的读写发生缓冲区满或缓冲区空write/read会返回-1并设置errnoEAGAIN。而epoll只关心缓冲区非满和缓冲区非空事件。一个epoll模式的代码大概的样子是123456whiletrue { active_stream[] epoll_wait(epollfd) fori in active_stream[] { read or write till }}限于篇幅我只说这么多以揭示原理性的东西至于epoll的使用细节请参考man和google实现细节请参阅linux kernel source。上文提到了epoll可以将哪个流发生了什么事件主动通知而不需要轮询。那么到底是如何实现通知的呢这就和操作系统的原理相关在内核的最底层是中断类似系统回调的机制。网卡设备对应一个中断号, 当网卡收到网络端的消息的时候会向CPU发起中断请求, 然后CPU处理该请求. 通过驱动程序 进而操作系统得到通知, 系统然后通知epoll, epoll通知用户代码。它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合另一点原因就是获取事件的时候它无须遍历整个被侦听的描述符集只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll在被内核初始化时操作系统启动同时会开辟出epoll自己的内核高速cache区用于安置每一个我们想监控的socket这些socket会以红黑树的形式保存在内核cache里以支持快速的查找、插入、删除。这个内核高速cache区就是建立连续的物理内存页然后在之上建立slab层简单的说就是物理上分配好你想要的size的内存对象每次使用时都是使用空闲的已分配好的对象。所以简单总结下epoll和select的区别进程通过将一个或多个fd传递给select或poll系统调用阻塞在select;这样select/poll可以帮我们侦测许多fd是否就绪但是select/poll是顺序扫描fd是否就绪而且支持的fd数量有限。linux还提供了一个epoll系统调用epoll是基于事件驱动方式而不是顺序扫描,当有fd就绪时立即回调函数rollback这种epoll的方式是不是能联想起Java nio关于nio请查看[高并发Java 八] NIO和AIO (https://my.oschina.net/hosee/blog/615269)。不过nio中的Selector的取名总让我以为是类似于select/poll的模型但是你会发现当有数据被准备好时调用完select()后会返回一个SelectionKeySelectionKey表示在某个selector上的某个Channel的数据已经被准备好了。那到底底层实现是使用select/poll还是epoll呢123456789101112131415161718192021222324252627publicstatic SelectorProvider create() {PrivilegedAction pa newGetPropertyAction(os.name);String osname (String) AccessController.doPrivileged(pa); if(SunOS.equals(osname)) { returnnew sun.nio.ch.DevPollSelectorProvider(); } // use EPollSelectorProvider for Linux kernels 2.6 if(Linux.equals(osname)) { pa newGetPropertyAction(os.version); String osversion (String) AccessController.doPrivileged(pa); String[] vers osversion.split(\\.,0); if(vers.length 2) { try{ intmajor Integer.parseInt(vers[0]); intminor Integer.parseInt(vers[1]); if(major 2|| (major 2 minor 6)) { returnnew sun.nio.ch.EPollSelectorProvider(); } }catch(NumberFormatException x) { // format not recognized } } } returnnew sun.nio.ch.PollSelectorProvider();}可以看到在Linux下内核版本大于2.6时使用epoll小于2.6时使用pollReference1. https://www.zhihu.com/question/20122137/answer/140491122. http://my.oschina.net/ielts0909/blog/89849