当前位置: 首页 > news >正文

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

用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的并发数量,因此考虑用子进程的方式增加进程数,提高服务能力上限。

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

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

测试代码

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

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

  1. 在线程池中运行的函数(这里是self.block_task)能够同时执行。表现为在控制台交替打印出数字。
  2. 两个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个进程在运行,且公用同一个端口。

 

转载于:https://www.cnblogs.com/zhichaoma/p/9667738.html

相关文章:

  • 数据库之左连接,右连接和内连接
  • C++ 引用
  • ES6 ...操作符
  • 2.Median of Two Sorted Arrays (两个排序数组的中位数)
  • 轻量级kotlin + Mvp + Rxjava + Retrofit框架
  • HDU 2722 Here We Go(relians) Again
  • yii2-queue一个好用的yii2队列操作扩展
  • ppwjs之bootstrap表格:响应式
  • [大牛翻译系列]Hadoop(22)附录D.2 复制连接框架
  • Java大小写转换
  • Transact-SQL语法速查手册
  • 开源地图数据可视化库——mapnik
  • IOS开发常用的linux命令
  • grep/字符/次数匹配/锚定符/小大括号/wc/tr/cut/sort/uniq
  • ajax跨域问题
  • [数据结构]链表的实现在PHP中
  • 【5+】跨webview多页面 触发事件(二)
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • Js基础知识(四) - js运行原理与机制
  • Laravel 中的一个后期静态绑定
  • Mysql优化
  • Travix是如何部署应用程序到Kubernetes上的
  • 你真的知道 == 和 equals 的区别吗?
  • 使用 Docker 部署 Spring Boot项目
  • 微信小程序设置上一页数据
  • ​Python 3 新特性:类型注解
  • #我与Java虚拟机的故事#连载18:JAVA成长之路
  • (07)Hive——窗口函数详解
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (C#)Windows Shell 外壳编程系列4 - 上下文菜单(iContextMenu)(二)嵌入菜单和执行命令...
  • (C语言)求出1,2,5三个数不同个数组合为100的组合个数
  • (M)unity2D敌人的创建、人物属性设置,遇敌掉血
  • (Note)C++中的继承方式
  • (SpringBoot)第七章:SpringBoot日志文件
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (论文阅读40-45)图像描述1
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (四)Controller接口控制器详解(三)
  • (新)网络工程师考点串讲与真题详解
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转载)Linux 多线程条件变量同步
  • .NET MVC第五章、模型绑定获取表单数据
  • .NET 回调、接口回调、 委托
  • .Net接口调试与案例
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .net利用SQLBulkCopy进行数据库之间的大批量数据传递
  • .Net中wcf服务生成及调用
  • .sdf和.msp文件读取
  • ;号自动换行
  • [ 第一章] JavaScript 简史
  • [BZOJ2281][SDOI2011]黑白棋(K-Nim博弈)
  • [CISCN2021 Quals]upload(PNG-IDAT块嵌入马)
  • [CUDA 学习笔记] CUDA kernel 的 grid_size 和 block_size 选择
  • [Enterprise Library]调用Enterprise Library时出现的错误事件之关闭办法