Epoll与异步非阻塞
来源:互联网 发布:linux打包文件夹 编辑:程序博客网 时间:2024/06/05 09:37
走出大学印象最深刻的就是异步的编程模型,还记得第一次见到奇怪的Epoll时的惊喜和兴奋,然后读了Tornado、Nginx、Redis等源码之后恍然大悟。本文主要以Tornado中的实现说明,所以主要使用Python代码,会加Redis的实现做对比,所以也会有部分的C代码,后期会加上Nginx的实现做对比。
- 异步和非阻塞的区别
首先说明两者不是一个层次的东西,非阻塞是对于socket而言,而异步是相对于应用程序而言,是一种编程模型,网上的很多文章对此解释不清,甚至混为一谈。
Epoll是非阻塞的,但不是异步的。实现非阻塞很简单,只要一句socket.setblocking(False)就够了。实现异步却很复杂,在《UNIX网络编程》中给出的5中I/O模型,只有最后一种是异步模型,其他的都是非异步的。
Linux没有实现异步IO(效率并不高),Epoll是一种I/O多路复用技术,用户程序需要主动的去询问内核是否有事件发生,而不是事件发生时内核主动的去调用回调函数,所以不是异步的。而Tornado等框架之所以声称是异步的,是框架在epoll的基础上进行了一层封装,由框架去取事件,然后由框架去调用用户的回调函数,所以对于基于框架的用户程序来说,是异步的。
阻塞模式下,内核收到I/O事件会唤醒处理者,而在非阻塞模式下,会将I/O事件放到其他对象(select,epoll)中甚至忽略。
Tornado使用Epoll实现了异步的编程模型,使用异步的前提是socket是非阻塞的,
- Epoll的使用
Epoll的使用只需要三个函数:
1、int epoll_create(int size);创建一个epoll句柄。内核会建立一系列的数据结构,比如就绪事件队列ready_list_link,具体数据结构下次再分析。
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);注册事件,将fd添加(或删除或修改)到刚创建的几个epoll数据结构中。
3、int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);查询是否有事件发生,这里一般是各种框架死循环的地方。
- Tornado对Epoll的封装
以Tornado的实现为例,对Epoll的封装是在ioloop模块中的IOLoop类。首先是一个单例模式,函数instance,用于获取全局唯一的epoll。最主要的是一个死循环,在start函数中,一直调用
try: event_pairs = self._impl.poll(poll_timeout) except Exception, e:也就是前面提到的epoll_wait函数,当event_pairs为空时,会阻塞在这里,当event_pairs不为空时,则去执行相应的handler,执行完再回来调用这个函数,整个进程会一直在这个函数中死循环。
Tornado是一个单线程框架,它不会像多线程那样,一个线程阻塞了,操作系统会调度另一个线程,如果一处阻塞,就会导致整个进程阻塞,这就要求任何代码都必须是非阻塞的,所以Tornado的iostream模块的IOStream类实现了对非阻塞的socket的封装(IOStream实现的功能远不只是非阻塞,它在socket之上加了一个缓存层,并且有一些方便HTTP协议调用的函数),如下
class IOStream(object): r"""A utility class to write to and read from a non-blocking socket. """ def __init__(self, socket, io_loop=None, max_buffer_size=104857600, read_chunk_size=4096): self.socket = socket self.socket.setblocking(False) self.io_loop = io_loop or ioloop.IOLoop.instance()可以看到self.socket.setblocking(False)将socket设置为了非阻塞,而self.io_loop则是单例模式的全局ioloop对象。对ioloop的操作在_add_io_state函数中
def _add_io_state(self, state): """Adds `state` (IOLoop.{READ,WRITE} flags) to our event handler. """ if self.socket is None: # connection has been closed, so there can be no future events return if self._state is None: self._state = ioloop.IOLoop.ERROR | state with stack_context.NullContext(): self.io_loop.add_handler( self.socket.fileno(), self._handle_events, self._state) elif not self._state & state: self._state = self._state | state self.io_loop.update_handler(self.socket.fileno(), self._state)可以看到调用了self.io_loop.add_handler函数和self.io_loop.update_handler函数,这两个函数都是对应上面提到的epoll_ctl函数,即添加、更新事件监听。
- Tornado的异步方式
def fetch(self, request, callback, **kwargs):对_HTTPConnection的调用是在_process_queue函数
def _process_queue(self): with stack_context.NullContext(): while self.queue and len(self.active) < self.max_clients: request, callback = self.queue.popleft() key = object() self.active[key] = (request, callback) _HTTPConnection(self.io_loop, self, request, functools.partial(self._release_fetch, key), callback, self.max_buffer_size)
_HTTPConnection类是对上面提到的IOStream类的一层封装,SimpleAsyncHTTPClient会将url和回调函数callback传入_HTTPConnection,而_HTTPConnection会调用IOStream通过IOLoop将事件和callback注册入Epoll中。
对于异步,多个回调函数时是很复杂的,回调函数中又包含回调函数,Tornado提供了gen模块,它利用yield语法,可以以同步的编程方式编写异步的代码。具体下次再分析。
- 优点
2、Epoll对于select,select是内核对数组置位的方式标识某个fd有事件发生,每次都要线性的遍历整个数组去判断是否有事件发生;而Epoll是内核将就绪事件用链表链接起来,每次只需要访问这个就绪链表,而不用访问未就绪的fd。Epoll相对于select的优势是去掉了轮询。
3、Epoll没有对fd个数的限制,而select有限制。
- 缺点
2、编程复杂,很多的回调函数是逻辑不清晰,也不利于调试,Tornado提供的gen模块改善了一下。
,
- Epoll与异步非阻塞
- 非阻塞/异步(epoll) openssl
- 非阻塞/异步(epoll) openssl
- 非阻塞/异步(epoll) openssl
- 阻塞与非阻塞 异步非阻塞
- (精辟)socket阻塞与非阻塞,同步与异步,select,poll,epoll
- socket阻塞与非阻塞 同步与异步 select poll epoll
- 非阻塞socket与epoll
- 计算机网络IO:阻塞、非阻塞、同步、异步以及select与epoll
- I/O阻塞非阻塞与异步 epoll总结 ET/LT AStar提速
- 阻塞、非阻塞、异步、同步以及select/poll和epoll
- 阻塞、非阻塞、异步、同步、I/O、select、poll、epoll
- 阻塞、非阻塞、异步、同步以及select/poll和epoll
- 阻塞、非阻塞、异步、同步以及select/poll和epoll
- 阻塞、非阻塞、异步、同步以及select/poll和epoll
- 阻塞、非阻塞、异步、同步以及select/poll和epoll
- 阻塞、非阻塞、异步、同步以及select/poll和epoll
- IO-同步异步,阻塞非阻塞,select, poll , epoll
- csdn英雄会(pongo)题解之回文数
- Spring mvc+hibernate+freemarker(开源项目)
- Java2D (WeatherWizard)
- ffmpeg简介
- 使用togglebutton切换背景颜色和字体颜色
- Epoll与异步非阻塞
- UVa 10139 Factovisors (阶乘能否整除?)
- MapReduce编程System.out.println输出在哪里可找到?
- javascript中IE浏览器不支持NEW DATE()带参数的解决方法 (转)
- ffmpeg教程
- SGU 271Book Pile(模拟 deque+stack)
- linux好的博客
- 怎样使用sysrq
- vim 环境配置