tornado异步非阻塞实现方式

目录

 

tornado异步非阻塞实现方式

1.多线程

2.老版协程

3.新版协程

实验

1.完全阻塞,同步代码

2.老版本协程

3新版本协程

4 多线程+新版协程,线程函数失效

5.线程+不声明异步,可以非阻塞

6.线程+声明异步  = 非阻塞

tornado框架的异步非阻塞

同步阻塞(Blocking)

异步非阻塞(Non Blocking)

tornado封装的协程+生成器

tornado封装的协程+Future

Python 3.5: async and await

线程池

多进程运行


tornado异步非阻塞实现方式


1.多线程


executor = ThreadPoolExecutor(max_workers=2) + @run_on_executor  + @gen.coroutine  

自己测试@run_on_executor 直接修饰get或post方法也非非阻塞,求解释

另外线程切换开销还是挺大的,测试的连续三次请求,假设点击耗时1.5s,看log三次请求切换时长增加了2.5s,大概切换3次线程耗时1s

计算密集型任务中速度:多进程 > 单进程/线程 > 多线程, IO 密集型任务速度: 多线程 > 多进程 > 单进程/线程。

2.老版协程

@gen.coroutine  + yield

3.新版协程

async + await      版本5.0以上

 

4.多进程

server.bind(8888)
server.start(2)  

实验

1.完全阻塞,同步代码

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import threading
import time

from loguru import logger
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor


class MainPage(RequestHandler):

    def background_task(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- {id} starting..")
        time.sleep(10)
        logger.info(f"{threading.currentThread()} -- {id} finish")


    def get(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- get id:{id}")
        self.background_task()
        logger.info(f"{id} return. cost : {self.request.request_time()}s")
        self.write(f"{id} return. cost : {self.request.request_time()}s")



if __name__ == "__main__":
    HTTPServer(Application([
        ("/", MainPage)
    ], debug=True)).listen(9999)
    logger.info("start web .")
    IOLoop.instance().start()

浏览器依次快速发送三个请求

http://localhost:9999/?id=1

http://localhost:9999/?id=2

http://localhost:9999/?id=3

结果:

/Users/logen/anaconda3/bin/python3.6 /Users/logen/PythonProject/mytest/tornadoT2.py
2020-11-21 21:56:00.288 | INFO     | __main__:<module>:41 - start web .
2020-11-21 21:56:21.654 | INFO     | __main__:get:30 - <_MainThread(MainThread, started 4423304640)> -- get id:1
2020-11-21 21:56:21.654 | INFO     | __main__:background_task:22 - <_MainThread(MainThread, started 4423304640)> -- 1 starting..
2020-11-21 21:56:31.658 | INFO     | __main__:background_task:24 - <_MainThread(MainThread, started 4423304640)> -- 1 finish
2020-11-21 21:56:31.658 | INFO     | __main__:get:32 - 1 return. cost : 10.005362033843994s
2020-11-21 21:56:31.670 | INFO     | __main__:get:30 - <_MainThread(MainThread, started 4423304640)> -- get id:3
2020-11-21 21:56:31.671 | INFO     | __main__:background_task:22 - <_MainThread(MainThread, started 4423304640)> -- 3 starting..
2020-11-21 21:56:41.675 | INFO     | __main__:background_task:24 - <_MainThread(MainThread, started 4423304640)> -- 3 finish
2020-11-21 21:56:41.675 | INFO     | __main__:get:32 - 3 return. cost : 10.0055091381073s
2020-11-21 21:56:41.676 | INFO     | __main__:get:30 - <_MainThread(MainThread, started 4423304640)> -- get id:2
2020-11-21 21:56:41.677 | INFO     | __main__:background_task:22 - <_MainThread(MainThread, started 4423304640)> -- 2 starting..
2020-11-21 21:56:51.682 | INFO     | __main__:background_task:24 - <_MainThread(MainThread, started 4423304640)> -- 2 finish
2020-11-21 21:56:51.683 | INFO     | __main__:get:32 - 2 return. cost : 20.012611150741577s
 

2.老版本协程

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import threading
import time

from loguru import logger
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor


class MainPage(RequestHandler):

    @gen.coroutine
    def background_task(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- {id} starting..")
        # time.sleep(10)  必须用异步的sleep,time的sleep会让主线程sleep,导致都阻塞
        yield gen.sleep(10)
        logger.info(f"{threading.currentThread()} -- {id} finish")

    @gen.coroutine
    def get(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- get id:{id}")
        yield self.background_task()
        logger.info(f"{id} return. cost : {self.request.request_time()}s")
        self.write(f"{id} return. cost : {self.request.request_time()}s")


# main 启动。
if __name__ == "__main__":
    HTTPServer(Application([
        ("/", MainPage)
    ], debug=True)).listen(9999)
    logger.info("start web .")
    IOLoop.instance().start()

结果

/Users/logen/anaconda3/bin/python3.6 /Users/logen/PythonProject/mytest/tornadoT2.py
2020-11-21 22:11:39.915 | INFO     | __main__:<module>:43 - start web .
2020-11-21 22:11:51.552 | INFO     | __main__:get:32 - <_MainThread(MainThread, started 4641727936)> -- get id:1
2020-11-21 22:11:51.552 | INFO     | __main__:background_task:23 - <_MainThread(MainThread, started 4641727936)> -- 1 starting..
2020-11-21 22:11:55.965 | INFO     | __main__:get:32 - <_MainThread(MainThread, started 4641727936)> -- get id:2
2020-11-21 22:11:55.966 | INFO     | __main__:background_task:23 - <_MainThread(MainThread, started 4641727936)> -- 2 starting..
2020-11-21 22:11:58.512 | INFO     | __main__:get:32 - <_MainThread(MainThread, started 4641727936)> -- get id:3
2020-11-21 22:11:58.512 | INFO     | __main__:background_task:23 - <_MainThread(MainThread, started 4641727936)> -- 3 starting..
2020-11-21 22:12:01.556 | INFO     | __main__:background_task:26 - <_MainThread(MainThread, started 4641727936)> -- 1 finish
2020-11-21 22:12:01.557 | INFO     | __main__:get:34 - 1 return. cost : 10.006109952926636s
2020-11-21 22:12:05.970 | INFO     | __main__:background_task:26 - <_MainThread(MainThread, started 4641727936)> -- 2 finish
2020-11-21 22:12:05.970 | INFO     | __main__:get:34 - 2 return. cost : 10.00606083869934s
2020-11-21 22:12:08.514 | INFO     | __main__:background_task:26 - <_MainThread(MainThread, started 4641727936)> -- 3 finish
2020-11-21 22:12:08.515 | INFO     | __main__:get:34 - 3 return. cost : 10.003233909606934s

3新版本协程

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import threading
import time

from loguru import logger
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor


class MainPage(RequestHandler):

    async def background_task(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- {id} starting..")
        # time.sleep(10)  必须用异步的sleep,time的sleep会让主线程sleep,导致都阻塞
        # yield gen.sleep(10)
        await gen.sleep(10)
        logger.info(f"{threading.currentThread()} -- {id} finish")


    async def get(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- get id:{id}")
        await self.background_task()
        logger.info(f"{id} return. cost : {self.request.request_time()}s")
        self.write(f"{id} return. cost : {self.request.request_time()}s")


# main 启动。
if __name__ == "__main__":
    HTTPServer(Application([
        ("/", MainPage)
    ], debug=True)).listen(9999)
    logger.info("start web .")
    IOLoop.instance().start()

 

结果

2020-11-21 22:14:18.472 | INFO     | __main__:<module>:44 - start web .
2020-11-21 22:14:25.247 | INFO     | __main__:get:33 - <_MainThread(MainThread, started 4526081472)> -- get id:1
2020-11-21 22:14:25.249 | INFO     | __main__:background_task:23 - <_MainThread(MainThread, started 4526081472)> -- 1 starting..
2020-11-21 22:14:26.899 | INFO     | __main__:get:33 - <_MainThread(MainThread, started 4526081472)> -- get id:2
2020-11-21 22:14:26.899 | INFO     | __main__:background_task:23 - <_MainThread(MainThread, started 4526081472)> -- 2 starting..
2020-11-21 22:14:28.890 | INFO     | __main__:get:33 - <_MainThread(MainThread, started 4526081472)> -- get id:3
2020-11-21 22:14:28.890 | INFO     | __main__:background_task:23 - <_MainThread(MainThread, started 4526081472)> -- 3 starting..
2020-11-21 22:14:35.253 | INFO     | __main__:background_task:27 - <_MainThread(MainThread, started 4526081472)> -- 1 finish
2020-11-21 22:14:35.254 | INFO     | __main__:get:35 - 1 return. cost : 10.008929014205933s
2020-11-21 22:14:36.902 | INFO     | __main__:background_task:27 - <_MainThread(MainThread, started 4526081472)> -- 2 finish
2020-11-21 22:14:36.902 | INFO     | __main__:get:35 - 2 return. cost : 10.003278970718384s
2020-11-21 22:14:38.896 | INFO     | __main__:background_task:27 - <_MainThread(MainThread, started 4526081472)> -- 3 finish
2020-11-21 22:14:38.896 | INFO     | __main__:get:35 - 3 return. cost : 10.007110834121704s

 

4 多线程+新版协程,线程函数失效

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import threading
import time

from loguru import logger
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor


class MainPage(RequestHandler):
    # 必须定义一个executor的属性,然后run_on_executor 注解才管用。
    executor = ThreadPoolExecutor(max_workers=2)

    @run_on_executor  # 标记成后台程序执行。
    async def background_task(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- {id} starting..")
        # time.sleep(10)  必须用异步的sleep,time的sleep会让主线程sleep,导致都阻塞
        # yield gen.sleep(10)
        await gen.sleep(10)
        logger.info(f"{threading.currentThread()} -- {id} finish")

    async def get(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- get id:{id}")
        await self.background_task()
        logger.info(f"{id} return. cost : {self.request.request_time()}s")
        self.write(f"{id} return. cost : {self.request.request_time()}s")


# main 启动。
if __name__ == "__main__":
    HTTPServer(Application([
        ("/", MainPage)
    ], debug=True)).listen(9999)
    logger.info("start web .")
    IOLoop.instance().start()

2020-11-21 22:16:34.672 | INFO     | __main__:<module>:44 - start web .
2020-11-21 22:16:57.506 | INFO     | __main__:get:33 - <_MainThread(MainThread, started 4645803456)> -- get id:1
2020-11-21 22:16:57.506 | INFO     | __main__:get:35 - 1 return. cost : 0.0026171207427978516s
/Users/logen/anaconda3/lib/python3.6/asyncio/base_events.py:1432: RuntimeWarning: coroutine 'background_task' was never awaited
  handle = None  # Needed to break cycles when an exception occurs.
2020-11-21 22:16:59.514 | INFO     | __main__:get:33 - <_MainThread(MainThread, started 4645803456)> -- get id:2
2020-11-21 22:16:59.514 | INFO     | __main__:get:35 - 2 return. cost : 0.0013380050659179688s
2020-11-21 22:17:02.754 | INFO     | __main__:get:33 - <_MainThread(MainThread, started 4645803456)> -- get id:3
2020-11-21 22:17:02.756 | INFO     | __main__:get:35 - 3 return. cost : 0.0016019344329833984s

 

5.线程+不声明异步,可以非阻塞

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import threading
import time

from loguru import logger
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor


class MainPage(RequestHandler):
    # 必须定义一个executor的属性,然后run_on_executor 注解才管用。
    executor = ThreadPoolExecutor(max_workers=5)

    # @run_on_executor  # 标记成后台程序执行。
    # @gen.coroutine
    def background_task(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- {id} starting..")
        time.sleep(10)  #必须用异步的sleep,time的sleep会让主线程sleep,导致都阻塞
        # yield gen.sleep(10)
        # gen.sleep(10)
        logger.info(f"{threading.currentThread()} -- {id} finish")

    # @gen.coroutine
    @run_on_executor  # 标记成后台程序执行。
    def get(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- get id:{id}")
        self.background_task()
        logger.info(f"{id} return. cost : {self.request.request_time()}s")
        self.write(f"{id} return. cost : {self.request.request_time()}s")


# main 启动。
if __name__ == "__main__":
    HTTPServer(Application([
        ("/", MainPage)
    ], debug=True)).listen(9999)
    logger.info("start web .")
    IOLoop.instance().start()

结果

2020-11-21 22:27:17.309 | INFO     | __main__:<module>:44 - start web .
2020-11-21 22:27:22.019 | INFO     | __main__:get:33 - <Thread(ThreadPoolExecutor-0_0, started daemon 123145573457920)> -- get id:1
2020-11-21 22:27:22.019 | INFO     | __main__:background_task:23 - <Thread(ThreadPoolExecutor-0_0, started daemon 123145573457920)> -- 1 starting..
2020-11-21 22:27:24.074 | INFO     | __main__:get:33 - <Thread(ThreadPoolExecutor-0_1, started daemon 123145578713088)> -- get id:2
2020-11-21 22:27:24.074 | INFO     | __main__:background_task:23 - <Thread(ThreadPoolExecutor-0_1, started daemon 123145578713088)> -- 2 starting..
2020-11-21 22:27:26.059 | INFO     | __main__:get:33 - <Thread(ThreadPoolExecutor-0_2, started daemon 123145583968256)> -- get id:3
2020-11-21 22:27:26.060 | INFO     | __main__:background_task:23 - <Thread(ThreadPoolExecutor-0_2, started daemon 123145583968256)> -- 3 starting..
2020-11-21 22:27:32.021 | INFO     | __main__:background_task:27 - <Thread(ThreadPoolExecutor-0_0, started daemon 123145573457920)> -- 1 finish
2020-11-21 22:27:32.022 | INFO     | __main__:get:35 - 1 return. cost : 10.005007982254028s
2020-11-21 22:27:34.079 | INFO     | __main__:background_task:27 - <Thread(ThreadPoolExecutor-0_1, started daemon 123145578713088)> -- 2 finish
2020-11-21 22:27:34.079 | INFO     | __main__:get:35 - 2 return. cost : 10.006136178970337s
2020-11-21 22:27:36.065 | INFO     | __main__:background_task:27 - <Thread(ThreadPoolExecutor-0_2, started daemon 123145583968256)> -- 3 finish
2020-11-21 22:27:36.065 | INFO     | __main__:get:35 - 3 return. cost : 10.007701873779297s

6.线程+声明异步  = 非阻塞

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import threading
import time

from loguru import logger
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application, RequestHandler
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor


class MainPage(RequestHandler):
    # 必须定义一个executor的属性,然后run_on_executor 注解才管用。
    executor = ThreadPoolExecutor(max_workers=5)

    @run_on_executor  # 标记成后台程序执行。
    def background_task(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- {id} starting..")
        time.sleep(10)  #必须用异步的sleep,time的sleep会让主线程sleep,导致都阻塞
        # yield gen.sleep(10)
        # gen.sleep(10)
        logger.info(f"{threading.currentThread()} -- {id} finish")

    @gen.coroutine
    def get(self):
        id = self.get_argument('id', 'None')
        logger.info(f"{threading.currentThread()} -- get id:{id}")
        yield self.background_task()
        logger.info(f"{id} return. cost : {self.request.request_time()}s")
        self.write(f"{id} return. cost : {self.request.request_time()}s")


# main 启动。
if __name__ == "__main__":
    HTTPServer(Application([
        ("/", MainPage)
    ], debug=True)).listen(9999)
    logger.info("start web .")
    IOLoop.instance().start()

2020-11-21 22:29:35.251 | INFO     | __main__:<module>:42 - start web .
2020-11-21 22:29:44.443 | INFO     | __main__:get:31 - <_MainThread(MainThread, started 4388025792)> -- get id:1
2020-11-21 22:29:44.444 | INFO     | __main__:background_task:22 - <Thread(ThreadPoolExecutor-0_0, started daemon 123145507430400)> -- 1 starting..
2020-11-21 22:29:47.439 | INFO     | __main__:get:31 - <_MainThread(MainThread, started 4388025792)> -- get id:2
2020-11-21 22:29:47.440 | INFO     | __main__:background_task:22 - <Thread(ThreadPoolExecutor-0_1, started daemon 123145512685568)> -- 2 starting..
2020-11-21 22:29:49.278 | INFO     | __main__:get:31 - <_MainThread(MainThread, started 4388025792)> -- get id:3
2020-11-21 22:29:49.279 | INFO     | __main__:background_task:22 - <Thread(ThreadPoolExecutor-0_2, started daemon 123145517940736)> -- 3 starting..
2020-11-21 22:29:54.450 | INFO     | __main__:background_task:26 - <Thread(ThreadPoolExecutor-0_0, started daemon 123145507430400)> -- 1 finish
2020-11-21 22:29:54.450 | INFO     | __main__:get:33 - 1 return. cost : 10.009276151657104s
2020-11-21 22:29:57.446 | INFO     | __main__:background_task:26 - <Thread(ThreadPoolExecutor-0_1, started daemon 123145512685568)> -- 2 finish
2020-11-21 22:29:57.447 | INFO     | __main__:get:33 - 2 return. cost : 10.008260011672974s
2020-11-21 22:29:59.284 | INFO     | __main__:background_task:26 - <Thread(ThreadPoolExecutor-0_2, started daemon 123145517940736)> -- 3 finish
2020-11-21 22:29:59.285 | INFO     | __main__:get:33 - 3 return. cost : 10.00766110420227s

 

 

另一篇类似实验博客

tornado框架的异步非阻塞

https://www.cnblogs.com/becker/p/9335136.html

Tornado默认是单进程单线程。实时的web特性通常需要为每个用户一个大部分时间都处于空闲的长连接. 在传统的同步web服务器中,这意味着需要给每个用户分配一个专用的线程,这样的开销是十分巨大的.

为了减小对于并发连接需要的开销,Tornado使用了一种单线程事件循环的方式. 这意味着所有应用程序代码都应该是异步和非阻塞的,因为在同一时刻只有一个操作是有效的.

Tornado 中推荐用 协程 来编写异步代码. 协程使用 Python 中的关键字 yield 来替代链式回调来实现挂起和继续程序的执行(像在 gevent 中使用的轻量级线程合作的方法有时也称作协程, 但是在 Tornado 中所有协程使用异步函数来实现的明确的上下文切换).

同步阻塞(Blocking)

一个函数通常在它等待返回值的时候被 阻塞 .一个函数被阻塞可能由于很多原因: 网络I/O,磁盘I/O,互斥锁等等.事实上, 每一个 函数都会被阻塞,只是时间会比较短而已, 当一个函数运行时并且占用CPU(举一个极端的例子来说明为什么CPU阻塞的时间必须考虑在内, 考虑以下密码散列函数像bcrypt, 这个函数需要占据几百毫秒的CPU时间, 远远超过了通常对于网络和磁盘请求的时间). 一个函数可以在某些方面阻塞而在其他方面不阻塞.举例来说, tornado.httpclient 在默认设置下将阻塞与DNS解析,但是在其它网络请求时不会阻塞 (为了减轻这种影响,可以用 ThreadedResolver 或通过正确配置 libcurl 使用 tornado.curl_httpclient ). 在Tornado的上下文中我们通常讨论网络I/O上下文阻塞, 虽然各种阻塞已经被最小化了.


#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Lian
# Python 3.5
import time
import tornado.web
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
def doing():
    time.sleep(10)
    return 'Blocking'
class BlockingHandler(tornado.web.RequestHandler):
    def get(self):
        result = doing()
        self.write(result)
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/blocking", BlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
    

浏览器访问:http://127.0.0.1:8888/index
浏览器访问:http://127.0.0.1:8888/blocking
你会发现blocking会一直在转圈,处于一个堵塞状态。
你再访问index页面,你发现index页面也会堵塞住。

异步非阻塞(Non Blocking)

一个 异步 函数在它结束前就已经返回了,而且通常会在程序中触发一些动作然后在后台执行一些任务. (和正常的 同步 函数相比, 同步函数在返回之前做完了所有的事). 这里有几种类型的异步接口:

  • 回调函数(基本不用)
  • tornado协程+生成器
  • tornado协程+Future
  • 线程池进程池

tornado封装的协程+生成器


#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
import tornado.web
from tornado import gen
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
@gen.coroutine
def doing():
    """
    穿上@gen.coroutine 装饰器之后,最终结果会返回一个可以被yield 的生成器 Future 对象
    与众不同的是这个函数的返回值需要以 raise gen.Return() 这种形式返回。
    :return: Future object
    """
    # time.sleep(10)     # time.sleep() 是blocking 的,不支持异步操作,我刚开始测试tornado的时候坑了
    yield gen.sleep(10)  # 使用这个方法代替上面的方法模拟 I/O 等待的情况, 可以点进去看下这个方法的介绍
    raise gen.Return('Non-Blocking')
class NonBlockingHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        result = yield doing()
        self.write(result)
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
    

浏览器访问:http://127.0.0.1:8888/nonblocking
浏览器访问:http://127.0.0.1:8888/index
你会发现nonblocking会一直在转圈,处于一个堵塞状态。
你再访问index页面,你发现index页面能够访问不受影响。
包含了 yield 关键字的函数是一个 生成器(generator). 所有的生成器都是异步的; 当调用它们的时候,会返回一个生成器对象,而不是一个执行完的结果. @gen.coroutine 装饰器通过 yield 表达式和生成器进行交流, 而且通过返回一个 Future 与协程的调用方进行交互. 协程一般不会抛出异常: 它们抛出的任何异常将被 Future 捕获 直到它被得到. 这意味着用正确的方式调用协程是重要的, 否则你可能有被 忽略的错误。@gen.coroutine 可以让你的函数以异步协程的形式运行,但是依赖第三方的异步库,要求你的函数本身不是blocking的。例如上面的os.sleep() 方法是blocking 的,没办法实现异步非阻塞。

tornado封装的协程+Future

上面提到Future 到底是什么呢,原始的 Future 版本十分复杂, 但是 Futures 是 Tornado 中推荐使用的一种做法, 因为它有两个主要的优势. 错误处理时通过 Future.result 函数可以简单的抛出一个异常 (不同于某些传统的基于回调方式接口的 一对一的错误处理方式), 而且 Futures 对于携程兼容的很好. 我们这里简单使用一下future 写一个异步函数。


#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
import tornado.web
from tornado import gen
from tornado.concurrent import Future
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
def doing():
    future = Future()
    # here doing some things ...
    future.set_result('Non-Blocking')
    return future
class NonBlockingHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        result = yield doing()
        self.write(result)
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
    

Python 3.5: async and await

官方还介绍了在另一种写法, Python 3.5 引入了 async 和 await 关键字(使用这些关键字的 函数也被称为”原生协程”). 从Tornado 4.3, 你可以用它们代替 yield 为基础的协程. 只需要简单的使用 async def foo() 在函数定义的时候代替 @gen.coroutine 装饰器, 用 await 代替yield. 本文档的其他部分会继续使用 yield的风格来和旧版本的Python兼容, 但是如果 async 和 await 可用的话,它们运行起来会更快


#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
import tornado.web
from tornado import gen
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
async def doing():
    await gen.sleep(10)  # here are doing some things
    return 'Non-Blocking'
class NonBlockingHandler(tornado.web.RequestHandler):
    async def get(self):
        result = await doing()
        self.write(result)
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

并行执行


#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
import tornado.web
from tornado import gen
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
@gen.coroutine
def doing():
    yield gen.sleep(10)
    raise gen.Return('Non-Blocking')
class NonBlockingHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        result1, result2 = yield [doing(), doing()]
        self.write(result1)
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
    

那async ,await 那种方式能并行执行吗? 答案也是可以的:


#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
# Date: 2017/12/13
import tornado.web
from tornado import gen
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
async def doing():
    await gen.sleep(10)
    return 'Non-Blocking'
class NonBlockingHandler(tornado.web.RequestHandler):
    async def get(self):
        result1, result2 = await gen.convert_yielded([doing(), doing()])
        self.write(result1)
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

await 关键字比 yield 关键字功能要少一些. 例如,在一个使用 yield 的协程中, 你可以得到Futures 列表, 你也可以使用 tornado.gen.convert_yielded 来把任何使用 yield 工作的代码转换成使用 await 的形式.

线程池

coroutine 是给Non-blocking 函数提供异步协程的方式运行, ThreadPoolExecutor 则可以给blocking 的函数提供异步的方式运行,但是由于是多线程的,Python 使用多线程对性能来说是需要谨慎的,大量的计算量的情况可能会造成性能的下降。


#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
import time
import os
import tornado.web
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
        self.write('index')
        print('index')
class NonBlockingHandler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(4)
    @gen.coroutine
    def get(self):
        result = yield self.doing()
        self.write(result)
        print(result)
    # 使用tornado 线程池不需要加上下面的装饰器到I/O函数
    @run_on_executor
    def doing(self):
        # time.sleep(10)
        # yield gen.sleep(10)
        os.system("ping -c 20 www.baidu.com")  # 模拟I/O 任务
        return 'Non-Blocking'
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

设置超时时间

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
import time
import datetime
import os
import tornado.web
from tornado import gen
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
        print('index')
class NonBlockingHandler(tornado.web.RequestHandler):
    executor = ThreadPoolExecutor(4)
    @gen.coroutine
    def get(self):
        try:
            start = time.time()
            # 并行执行
            result1, result2 = yield gen.with_timeout(datetime.timedelta(seconds=5), [self.doing(1), self.doing(2)], quiet_exceptions=tornado.gen.TimeoutError)
            self.write("NO Timeout")
            print(result1, result2)
            print(time.time() - start)
        except gen.TimeoutError:
            self.write("Timeout")
            print("Timeout")
            print(time.time() - start)
    # 使用tornado 线程池需要加上下面的装饰器到I/O函数
    @run_on_executor
    def doing(self, num):
        time.sleep(10)
        return 'Non-Blocking%d' % num
application = tornado.web.Application([
    (r"/index", IndexHandler),
    (r"/nonblocking", NonBlockingHandler),
])
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

多进程运行

#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author: Liang Xian Sen
# Python 3.5
import tornado.web
from tornado import gen
from tornado.httpserver import HTTPServer
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('index')
@gen.coroutine
def doing():
    yield gen.sleep(10)
    raise gen.Return('Non-Blocking')
class NonBlockingHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self):
        result = yield doing()
        self.write(result)
def make_app():
    return tornado.web.Application([
        (r"/index", IndexHandler),
        (r"/nonblocking", NonBlockingHandler),
    ])
def main():
    app = make_app()
    server = HTTPServer(app)
    server.bind(8888)
    server.start(2)  # 设置启动多少个进程
    tornado.ioloop.IOLoop.current().start()
if __name__ == "__main__":
    main()

 

 

 

Tornado可以通过`@tornado.web.stream_request_body`装饰器来实现异步并行非阻塞的Server-Sent Events。具体步骤如下: 1. 创建一个继承自`tornado.web.RequestHandler`的类,添加`@tornado.web.stream_request_body`装饰器。 ```python import tornado.web class SSEHandler(tornado.web.RequestHandler): @tornado.web.stream_request_body def post(self): pass ``` 2. 在`post`方法中,使用`tornado.ioloop.IOLoop.current().spawn_callback`方法来异步执行数据的生成和推送,并使用`tornado.web.RequestHandler.flush`方法将数据发送给客户端。 ```python import tornado.web import tornado.ioloop class SSEHandler(tornado.web.RequestHandler): @tornado.web.stream_request_body def post(self): self.set_header('Content-Type', 'text/event-stream') self.set_header('Cache-Control', 'no-cache') self.set_header('Connection', 'keep-alive') def generate_data(): data = 'data: Hello, world!\n\n' self.write(data) self.flush() tornado.ioloop.IOLoop.current().spawn_callback(generate_data) ``` 在这个例子中,我们设置了响应头,告诉客户端我们要发送的是Server-Sent Events。然后在`generate_data`函数中,生成一条消息并使用`write`方法将其写入响应中,再使用`flush`方法将数据发送给客户端。最后,在`post`方法中使用`IOLoop.current().spawn_callback`方法来异步执行`generate_data`函数。 3. 在客户端使用`EventSource`对象来接收推送的数据。 ```javascript var source = new EventSource('/sse'); source.onmessage = function(event) { console.log(event.data); }; ``` 在客户端中,我们使用`EventSource`对象来向服务器发送请求,并监听`onmessage`事件来接收推送的数据。在接收到数据时,我们可以在控制台中打印出来。 以上就是使用Tornado实现异步并行非阻塞的Server-Sent Events的步骤。在实际应用中,可以在`generate_data`函数中根据需要生成不同的数据,并使用`write`和`flush`方法将数据推送给客户端。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值