Tornado 多进程实现分析详解
引子
Tornado是一个网络异步的的web开发框架,并且可以利用多进程进行提高效率,下面是创建一个多进程tornado程序的例子.
#!/usr/bin/envpython #-*-coding:utf-8-*- importos importtime importtornado.web importtornado.httpserver importtornado.ioloop importtornado.netutil importtornado.process classLongHandler(tornado.web.RequestHandler): defget(self): self.write(str(os.getpid())) time.sleep(10) if__name__=="__main__": app=tornado.web.Application(([r'/',LongHandler],)) sockets=tornado.netutil.bind_sockets(8090) tornado.process.fork_processes(2) server=tornado.httpserver.HTTPServer(app) server.add_sockets(sockets) tornado.ioloop.IOLoop.instance().start()
上面代码使用tornado.process.fork_processes创建了2个子进程,同时用时访问这个服务两次,分别会返回两个相邻的pid.可以看到tornado确实使用了两个进程来同时完成任务.
我一直很好奇tornado是如何将请求调度到子进程,多个子进程又如何不同时处理一个请求呢?
探究
我们首先是调用tornado.netutil.bind_sockets来创建一个socket(或一个socket列表),
接着我们调用tornado.process.fork_processes来fork子进程,阅读此函数的代码会发现这个函数仅仅是创建子进程,然后主进程负责等待子进程,如果子进程退出则会根据条件重启子进程,如果子进程全部退出并不符合重启条件,则主进程退出.
调用这个函数之后,子进程中函数会返回,子进程则继续执行调用这个函数之后的代码.
我们在fork子进程后做了如下操作.
server=tornado.httpserver.HTTPServer(app) server.add_sockets(sockets) tornado.ioloop.IOLoop.instance().start()
我们先看看tornado.httpserver.HTTPServer.add_sockets发现HTTPServer是继承的tornado.netutil.TCPServer,add_sockets也是实现在TCPServer中
tornado.netutil.TCPServer.add_sockets
defadd_sockets(self,sockets): ifself.io_loopisNone: self.io_loop=IOLoop.instance() forsockinsockets: self._sockets[sock.fileno()]=sock add_accept_handler(sock,self._handle_connection, io_loop=self.io_loop)
主要是映射了下socket和socket对应的文件描述符,我们看看它调用的add_accept_handler
defadd_accept_handler(sock,callback,io_loop=None): ifio_loopisNone: io_loop=IOLoop.instance() defaccept_handler(fd,events): whileTrue: try: connection,address=sock.accept() exceptsocket.errorase: ife.args[0]in(errno.EWOULDBLOCK,errno.EAGAIN): return raise callback(connection,address) io_loop.add_handler(sock.fileno(),accept_handler,IOLoop.READ)
我们知道I/O多路复用在处理服务端socket时,当有连接请求过来时,会触发可读的事件,此函数将socket在主事件循环中注册读事件(IOLoop.READ),它的回调会创建连接,我注意到回调里的异常捕获有这样几行
ife.args[0]in(errno.EWOULDBLOCK,errno.EAGAIN): return raise
发现在创建连接的时候会跳过这个异常呢,为什么?那么EWOULDBLOCK和EAGAIN是是什么呢?通过查找知道它的意思是在非阻塞模式下,不需要重读或重写,EAGAIN是EWOULDBLOCK在Windows上的名字,所以看到这里就很明确了.
结论
Tornado多进程的处理流程是先创建socket,然后再fork子进程,这样所有的子进程实际都监听一个(或多个)文件描述符,也就是都在监听同样的socket.
当连接过来所有的子进程都会收到可读事件,这时候所有的子进程都会跳到accept_handler回调函数,尝试建立连接.
一旦其中一个子进程成功的建立了连接,当其他子进程再尝试建立这个连接的时候就会触发EWOULDBLOCK(或EAGAIN)错误.这时候回调函数判断是这个错误则返回函数不做处理.
当成功建立连接的子进程还在处理这个连接的时候又过来一个连接,这时候就会有另外一个子进程接手这个连接.
Tornado就是通过这样一种机制,利用多进程提升效率,由于连接只能由一个子进程成功创建,同一个请求也就不会被多个子进程处理.
后记
写完才发现,我所使用的代码是tornado-2.4.post2版本,当前最新代码是3.3.0,查看了下最新代码,最新代码TCPServer写到单独tornado.tcpserver里了,其他和本文相关的并没有什么大的变化.
Category:PythonTagged:Pythonfork_processestornado多进程web提升效率
以上就是本文关于Tornado多进程实现分析详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!