Tornado实现多进程/多线程的HTTP服务详解


Posted in Python onJuly 25, 2019

用tornado web服务的基本流程

1.实现处理请求的Handler,该类继承自tornado.web.RequestHandler,实现用于处理请求的对应方法如:get、post等。返回内容用self.write方法输出。

2.实例化一个Application。构造函数的参数是一个Handlers列表,通过正则表达式,将请求与Handler对应起来。通过dict将Handler需要的其他对象以参数的方式传递给Handler的initialize方法。

3.初始化一个tornado.httpserver.HTTPServer对象,构造函数的参数是上一步的Application对象。

4.为HTTPServer对象绑定一个端口。

5.开始IOLoop。

需要用到的特性

由于tornado的亮点是异步请求,所以这里首先想到的是将所有请求都改造为异步的。但是这里遇到一个问题,就是异步函数内一定不能有阻塞调用出现,否则整个IOLoop都会被卡住。这就要求彻底地去改造服务,将所有IO或是用时较长的请求都改造为异步函数。这个工程量是非常大的,需要去修改已有的代码。因此,我们考虑用线程池的方式去实现。当一个线程阻塞在某个请求或IO时,其他线程或IOLoop会继续执行。

另外一个瓶颈就是GIL限制了CPU的并发数量,因此考虑用子进程的方式增加进程数,提高服务能力上限。

综合上面的分析,大致用以下方案:

  • 通过子进程的方式复制多个进程,使子进程中的只读页指向同一个物理页。
  • 线程池。回避异步改造的工作量,增加IO的并发量。

测试代码

首先测试线程池,测试用例为:

对sleep页面同时发出两个请求:

  • 在线程池中运行的函数(这里是self.block_task)能够同时执行。表现为在控制台交替打印出数字。
  • 两个get请求几乎同时返回,在浏览器上显示返回的内容。

线程池的测试代码如下:

import os
import sys
import time
 
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
from tornado.options import define, options
 
class HasBlockTaskHandler(tornado.web.RequestHandler):
  executor = ThreadPoolExecutor(20)  #起线程池,由当前RequestHandler持有
   
  @tornado.gen.coroutine
  def get(self):
    strTime = time.strftime("%Y-%m-%d %H:%M:%S")
    print "in get before block_task %s" % strTime
    result = yield self.block_task(strTime)
    print "in get after block_task"
    self.write("%s" % (result))
 
  @run_on_executor
  def block_task(self, strTime):
    print "in block_task %s" % strTime
    for i in range(1, 16):
      time.sleep(1)
      print "step %d : %s" % (i, strTime)
    return "Finish %s" % strTime
 
if __name__ == "__main__":
  tornado.options.parse_command_line()
  app = tornado.web.Application(handlers=[(r"/sleep", HasBlockTaskHandler)], autoreload=False, debug=False)
  http_server = tornado.httpserver.HTTPServer(app)
  http_server.bind(8888)
  tornado.ioloop.IOLoop.instance().start()

整个代码里有几个位置值得关注:

1.executor = ThreadPoolExecutor(20)。这是给Handler类初始化了一个线程池。其中concurrent.futures不属于tornado,是python的一个独立模块,在python3中是内置模块,python2.7需要自己安装。

2.修饰符@run_on_executor。这个修饰符将同步函数改造为在executor(这里是线程池)上运行的异步函数,内部实现是将被修饰的函数submit到executor,返回一个Future对象。

3.修饰符@tornado.gen.coroutine。被这个修饰符修饰的函数,是一个以同步函数方式编写的异步函数。原本通过callback方式编写的异步代码,有了这个修饰符,可以通过yield一个Future的方式来写。被修饰的函数在yield了一个Future对象后将会被挂起,Future对象的结果返回后继续执行。

运行代码后,在两个不同浏览器上访问sleep页面,得到了想要的效果。这里有一个小插曲,就是如果在同一浏览器的两个tab上进行测试,是无法看到想要的效果。第二个get请求会被block,直到第一个get请求返回,服务端才开始处理第二个get请求。这让我一度觉得多线程没有生效,用了半天时间查了很多资料,才看到是浏览器把相同的第二个请求block了,具体链接参考这里。

由于tornado很方便地支持多进程模型,多进程的使用要简单很多,在以上例子中,只需要对启动部分稍作改动即可。具体代码如下所示:

if __name__ == "__main__":
  tornado.options.parse_command_line()
  app = tornado.web.Application(handlers=[(r"/sleep", HasBlockTaskHandler)], autoreload=False, debug=False)
  http_server = tornado.httpserver.HTTPServer(app)
  http_server.bind(8888)
  print tornado.ioloop.IOLoop.initialized()
  http_server.start(5)
  tornado.ioloop.IOLoop.instance().start()

需要注意的地方有两点:

1.app = tornado.web.Application(handlers=[(r"/sleep", HasBlockTaskHandler)], autoreload=False, debug=False),在生成Application对象时,要将autoreload和debug两个参数至为False。也就是需要保证在fork子进程之前IOLoop是未被初始化的。这个可以通过tornado.ioloop.IOLoop.initialized()函数来跟。

2.http_server.start(5)在启动IOLoop之前通过start函数设置进程数量,如果设置为0表示每个CPU都启动一个进程。
最后的效果是可以看到n+1个进程在运行,且公用同一个端口。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
使用Node.js和Socket.IO扩展Django的实时处理功能
Apr 20 Python
python Socket之客户端和服务端握手详解
Sep 18 Python
python实现Floyd算法
Jan 03 Python
python导出hive数据表的schema实例代码
Jan 22 Python
python3+PyQt5 实现Rich文本的行编辑方法
Jun 17 Python
Python中判断子串存在的性能比较及分析总结
Jun 23 Python
python脚本后台执行方式
Dec 21 Python
Python统计文本词汇出现次数的实例代码
Feb 27 Python
selenium+python配置chrome浏览器的选项的实现
Mar 18 Python
解决pycharm编辑区显示yaml文件层级结构遇中文乱码问题
Apr 27 Python
python 负数取模运算实例
Jun 03 Python
在keras中model.fit_generator()和model.fit()的区别说明
Jun 17 Python
Python使用指定端口进行http请求的例子
Jul 25 #Python
django多对多表的创建,级联删除及手动创建第三张表
Jul 25 #Python
django 中QuerySet特性功能详解
Jul 25 #Python
对Python _取log的几种方式小结
Jul 25 #Python
django 中的聚合函数,分组函数,F 查询,Q查询
Jul 25 #Python
python使用paramiko模块通过ssh2协议对交换机进行配置的方法
Jul 25 #Python
python2 中 unicode 和 str 之间的转换及与python3 str 的区别
Jul 25 #Python
You might like
浅析php面向对象public private protected 访问修饰符
2013/06/30 PHP
php+mysql大量用户登录解决方案分析
2014/12/29 PHP
php+ajax实现文章自动保存的方法
2014/12/30 PHP
Laravel框架实现抢红包功能示例
2019/10/31 PHP
thinkphp5.1框架模板赋值与变量输出示例
2020/05/25 PHP
一页面多XMLHttpRequest对象
2007/01/22 Javascript
javascript级联下拉列表实例代码(自写)
2013/05/10 Javascript
js中一个函数获取另一个函数返回值问题探讨
2013/11/21 Javascript
ie浏览器使用js导出网页到excel并打印
2014/03/11 Javascript
javascript实现网页子页面遍历回调的方法(涉及 window.frames、递归函数、函数上下文)
2015/07/27 Javascript
举例讲解jQuery中可见性过滤选择器的使用
2016/04/18 Javascript
前端面试题及答案整理(二)
2016/08/26 Javascript
javascript使用闭包模拟对象的私有属性和方法
2016/10/05 Javascript
node.js版本管理工具n无效的原理和解决方法
2016/11/24 Javascript
深入理解Angularjs中的$resource服务
2016/12/31 Javascript
JS 中document.write()的用法和清空的原因浅析
2017/12/04 Javascript
js 实现复选框只能选择一项的示例代码
2018/01/23 Javascript
[01:01:52]完美世界DOTA2联赛PWL S2 GXR vs Magma 第二场 11.25
2020/11/26 DOTA
Python ORM框架SQLAlchemy学习笔记之数据添加和事务回滚介绍
2014/06/10 Python
Python标准库之随机数 (math包、random包)介绍
2014/11/25 Python
详解Python设计模式编程中观察者模式与策略模式的运用
2016/03/02 Python
Python编程实现的简单神经网络算法示例
2018/01/26 Python
python spyder中读取txt为图片的方法
2018/04/27 Python
Python中shapefile转换geojson的示例
2019/01/03 Python
python时间序列按频率生成日期的方法
2019/05/14 Python
python实现简易学生信息管理系统
2020/04/05 Python
Django自带的加密算法及加密模块详解
2019/12/03 Python
台湾时尚彩瞳专门店:imeime
2019/08/16 全球购物
护理工作感言
2014/01/16 职场文书
小学教师自我鉴定范文
2014/03/20 职场文书
租赁意向书范本
2014/04/01 职场文书
2014校长四风问题对照检查材料思想汇报
2014/09/16 职场文书
终止劳动合同协议书
2014/10/05 职场文书
高中校园广播稿
2014/10/21 职场文书
2014年学校工会工作总结
2014/12/06 职场文书
阿里云ECS云服务器快照的概念以及如何使用
2022/04/21 Servers