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 相关文章推荐
Python 文件重命名工具代码
Jul 26 Python
python连接mongodb操作数据示例(mongodb数据库配置类)
Dec 31 Python
Python中标准库OS的常用方法总结大全
Jul 19 Python
TensorFlow的权值更新方法
Jun 14 Python
Python将list中的string批量转化成int/float的方法
Jun 26 Python
详解将Django部署到Centos7全攻略
Sep 26 Python
对Python 3.2 迭代器的next函数实例讲解
Oct 18 Python
pandas中apply和transform方法的性能比较及区别介绍
Oct 30 Python
对python打乱数据集中X,y标签对的方法详解
Dec 14 Python
使用Django2快速开发Web项目的详细步骤
Jan 06 Python
Python 异常的捕获、异常的传递与主动抛出异常操作示例
Sep 23 Python
Pycharm生成可执行文件.exe的实现方法
Jun 02 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图片上传代码
2013/11/04 PHP
php自动载入类用法实例分析
2016/06/24 PHP
laravel 实现向公共模板中传值 (view composer)
2019/10/22 PHP
关于取不到由location.href提交而来的上级页面地址的解决办法
2009/07/30 Javascript
JavaScipt中的Math.ceil() 、Math.floor() 、Math.round() 三个函数的理解
2010/04/29 Javascript
javascript 节点排序 2
2011/01/31 Javascript
js中的异常处理try...catch使用介绍
2013/09/21 Javascript
详解jQuery的表单验证插件--Validation
2016/12/21 Javascript
js 函数式编程学习笔记
2017/03/25 Javascript
详解AngularJS1.x学习directive 中‘& ’‘=’ ‘@’符号的区别使用
2017/08/23 Javascript
vue数组对象排序的实现代码
2018/06/20 Javascript
vue中filters 传入两个参数 / 使用两个filters的实现方法
2019/07/15 Javascript
python解析文件示例
2014/01/23 Python
Python使用正则表达式实现文本替换的方法
2017/04/18 Python
深入理解Python中range和xrange的区别
2017/11/26 Python
Python操作word常见方法示例【win32com与docx模块】
2018/07/17 Python
Python3.5集合及其常见运算实例详解
2019/05/01 Python
对python中的*args与**kwgs的含义与作用详解
2019/08/28 Python
pytorch多GPU并行运算的实现
2019/09/27 Python
python 多线程共享全局变量的优劣
2020/09/24 Python
Python 多进程、多线程效率对比
2020/11/19 Python
de Bijenkorf比利时官网:荷兰最知名的百货商店
2017/06/29 全球购物
英国广泛的照明产品网站:Lights4living
2018/01/28 全球购物
自我鉴定书范文
2013/10/02 职场文书
毕业生就业自荐信
2013/12/04 职场文书
中层干部竞争上岗演讲稿
2014/01/13 职场文书
单位创先争优活动方案
2014/01/26 职场文书
社会实践先进工作者事迹材料
2014/05/06 职场文书
小学生环保倡议书
2014/05/15 职场文书
商场开业庆典策划方案
2014/06/02 职场文书
运动会横幅标语
2014/06/17 职场文书
公司党的群众路线教育实践活动领导班子对照检查材料
2014/09/25 职场文书
MySQL中连接查询和子查询的问题
2021/09/04 MySQL
解决IIS7下无法绑定https主机的问题
2022/04/29 Servers
Win11自动黑屏怎么办 Win11自动黑屏设置教程
2022/07/15 数码科技
前端canvas中物体边框和控制点的实现示例
2022/08/05 Javascript