Tornado开发之协程异步/多线程处理讲解

协程异步/多线程处理

以协程方式(Coroutine)进行异步处理,是Tornado推荐的方式。Coroutine不采用回调函数方式,而用yield来挂起和恢复运行(现在又有新关键字了,说明python的Coroutine还没有稳定下来),协程编程风格看起来像同步,但又没有线程的切换,因此效率要高一些。

在Tornado中,现在有两种方式实现协程:一是函数前加@tornado.gen.coroutine修饰器,异步函数前加yield,这是一种较“老”的方式;二是函数采用async定义,异步函数前加await。async和await是python3.5引入的,目前是python内置方式,因此后者的运行效率比前者要高些。但Tornado官网上也说了后者的不足:对老版本的兼容不如前者;yield适用性上也比await广些,例如面对future list时。

下面是以异步方式运行业务逻辑的三个页面示例。

import time
import tornado.ioloop
import tornado.web
import tornado.httpclient
import tornado.gen
 
class time_consuming_handler(tornado.web.RequestHandler):
    async def get(self):
        t1 =  time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
        result = await tornado.ioloop.IOLoop.current().run_in_executor(None, self.do_task,5)
        self.write("Task start at:[{}], return {}".format(t1,result))
    def do_task(self,sec):
        time.sleep(sec)
        return "done"
 
class fetch1_handler(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        t1 =  time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
        http_client = tornado.httpclient.AsyncHTTPClient()
        response = yield http_client.fetch("https://www.baidu.com")
        self.write("Task start at:[{}]\n".format(t1))       
        self.write(response.body)
 
class fetch2_handler(tornado.web.RequestHandler):
    async def get(self):
        t1 =  time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 
        http_client = tornado.httpclient.AsyncHTTPClient()
        response = await http_client.fetch("https://www.baidu.com")
        self.write("Task start at:[{}]\n".format(t1))       
        self.write(response.body)
         
if __name__ == "__main__":
    webapp=tornado.web.Application([
        (r"/fetch1", fetch1_handler),
        (r"/fetch2", fetch2_handler),       
        (r"/heavy", time_consuming_handler),
    ])
    webapp.listen(8000)
    tornado.ioloop.IOLoop.current().start()

"/fetch1"从百度获得页面内容,采用的是旧版的加修饰器方式,"/fetch2"与"/fetch1"功能一致,但是采用了新方式@tornado.gen.coroutine改为async,yield改为await即可。"/heavy"是一个将同步函数“封装”成异步函数的示例:do_task是一个阻塞的函数,将其放入tornado.ioloop.IOLoop.current().run_in_executor运行。run_in_executo的定义如下:

?

1

def run_in_executor(self, executor, func, *args)

executor是func的执行器,它应是concurrent.futures.Executor的子类,如果executor未指定,IOLoop将创建了一个线程池,最大线程数是cpu数的5倍:

?

1

self._executor = ThreadPoolExecutor(max_workers=(cpu_count() * 5))

因此,func(do_task)依然是按阻塞方式运行,只不过在另一线程中(同时运行的任务不能多于最大线程数,否则也要阻塞),看起来就变成非阻塞了。

上面的例子都比较简单,所以看起来异步操作也不麻烦(未涉及锁),Tornado也提供了必要的封装,但业务比较复杂时,写一个全部都是异步操作的web应用真不是件容易的事情,要求我们的思路要“跳跃”,相对比较难设计与调试。其实,多线程执行多任务运行并非一无是处,只是在任务很多时,效率不佳。

如果不采用异步处理,而又要提高效率,就必须采用下面描述的“多进程部署”了,此时,编程结构就“自然”多了。

你可能感兴趣的:(python)