Tornado 多进程实现分析详解


Posted in Python onJanuary 12, 2018

引子

Tornado 是一个网络异步的的web开发框架, 并且可以利用多进程进行提高效率, 下面是创建一个多进程 tornado 程序的例子.

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import time

import tornado.web
import tornado.httpserver
import tornado.ioloop
import tornado.netutil
import tornado.process


class LongHandler(tornado.web.RequestHandler):

	def get(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

def add_sockets(self, sockets):
		if self.io_loop is None:
			self.io_loop = IOLoop.instance()

		for sock in sockets:
			self._sockets[sock.fileno()] = sock
			add_accept_handler(sock, self._handle_connection,
							  io_loop=self.io_loop)

主要是映射了下 socket 和 socket 对应的文件描述符, 我们看看它调用的 add_accept_handler

def add_accept_handler(sock, callback, io_loop=None):
	if io_loop is None:
		io_loop = IOLoop.instance()

	def accept_handler(fd, events):
		while True:
			try:
				connection, address = sock.accept()
			except socket.error as e:
				if e.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), 它的回调 会创建连接, 我注意到回调里的异常捕获有这样几行

if e.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 多进程实现分析详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

Python 相关文章推荐
详解Python中的__new__()方法的使用
Apr 09 Python
python统计日志ip访问数的方法
Jul 06 Python
Python中单例模式总结
Feb 20 Python
Python采集猫眼两万条数据 对《无名之辈》影评进行分析
Dec 05 Python
Python编程在flask中模拟进行Restful的CRUD操作
Dec 28 Python
对python PLT中的image和skimage处理图片方法详解
Jan 10 Python
什么是Python中的匿名函数
Jun 02 Python
Python select及selectors模块概念用法详解
Jun 22 Python
python爬虫看看虎牙女主播中谁最“顶”步骤详解
Dec 01 Python
python 基于UDP协议套接字通信的实现
Jan 22 Python
Python环境搭建过程从安装到Hello World
Feb 05 Python
在Pycharm中安装Pandas库方法(简单易懂)
Feb 20 Python
快速了解Python相对导入
Jan 12 #Python
Python实现翻转数组功能示例
Jan 12 #Python
Python实现求数列和的方法示例
Jan 12 #Python
python+matplotlib演示电偶极子实例代码
Jan 12 #Python
Python实现读取及写入csv文件的方法示例
Jan 12 #Python
python+matplotlib绘制旋转椭圆实例代码
Jan 12 #Python
使用C++扩展Python的功能详解
Jan 12 #Python
You might like
使用HMAC-SHA1签名方法详解
2013/06/26 PHP
php不使用copy()函数复制文件的方法
2015/03/13 PHP
Yii2分页的使用及其扩展方法详解
2016/05/23 PHP
PHP静态方法和静态属性及常量属性的区别与介绍
2019/03/22 PHP
基于jQuery的固定表格头部的代码(IE6,7,8测试通过)
2010/05/18 Javascript
dwz 如何去掉ajaxloading具体代码
2013/05/22 Javascript
利用jquery.qrcode在页面上生成二维码且支持中文
2014/02/12 Javascript
JavaScript和CSS交互的方法汇总
2014/12/02 Javascript
JQuery调用绑定click事件的3种写法
2015/03/28 Javascript
jquery.gridrotator实现响应式图片展示画廊效果
2015/06/23 Javascript
微信小程序 支付功能开发错误总结
2017/02/21 Javascript
Vue.js项目部署到服务器的详细步骤
2017/07/17 Javascript
javascript实现Emrips反质数枚举的示例代码
2017/12/06 Javascript
vue如何实现自定义底部菜单栏
2019/07/01 Javascript
简单了解微信小程序 e.target与e.currentTarget的不同
2019/09/27 Javascript
深入理解Python分布式爬虫原理
2017/11/23 Python
Python subprocess模块功能与常见用法实例详解
2018/06/28 Python
Python基于递归算法求最小公倍数和最大公约数示例
2018/07/27 Python
浅谈django三种缓存模式的使用及注意点
2018/09/30 Python
Python3.5内置模块之shelve模块、xml模块、configparser模块、hashlib、hmac模块用法分析
2019/04/27 Python
利用Python进行图像的加法,图像混合(附代码)
2019/07/14 Python
python实现通过flask和前端进行数据收发
2019/08/22 Python
开启Django博客的RSS功能的实现方法
2020/02/17 Python
Html5+CSS3+EL表达式问题小结
2020/12/19 HTML / CSS
印度服装购物网站:Limeroad
2018/09/26 全球购物
元旦红领巾广播稿
2014/02/19 职场文书
司机职责范本
2014/03/08 职场文书
2014年党员承诺书范文
2014/05/20 职场文书
入党积极分子自我批评思想汇报
2014/10/10 职场文书
国际贸易实务实训报告
2014/11/05 职场文书
2015年三年级班主任工作总结
2015/05/21 职场文书
2015年七夕情人节感言
2015/08/03 职场文书
2016年教代会开幕词
2016/03/04 职场文书
读《瓦尔登湖》有感:每个人都需要一个瓦尔登湖
2019/10/17 职场文书
python Polars库的使用简介
2021/04/21 Python
pytorch锁死在dataloader(训练时卡死)
2021/05/28 Python