浅析tornado协程运行原理
admin
2023-07-31 00:45:47
0

前言

去年有一段时间一直在研究各种python协程框架,包括gevent, asyncio, tornado。阅读tornado的源码还是两个多月前的事了,一直想写一篇文章出来整理整理,但不知道从何处开始下笔。如果贴上一段段源码,然后通过语言来描述各种流程,这种类型的文章网上也有不少,况且这样子的讲解对于读者来说可能会比较乏味。

我希望我对于源码分析的博文能够通过贴上更容易理解的图(当然也会有一些代码来辅助讲解),这样的分享对读者来说会更加容易读懂,也更有价值。对自己要求高了,反而更难下笔,在试图画图的过程中,发现其实有好多细节自己也没有琢磨透,导致在如何组织这幅流程图的问题上斟酌了好久,不过好在最后终于捯饬出了一张自己觉得还算及格的流程图,作完图的时候我感觉比起之前刚阅读完代码时候的理解又上了一个层次。

流程图

tornado执行协程的方式有很多,但协程内部的运行原理没什么区别,这篇文章以IOLoop中的run_sync函数作为入口进行介绍。在开始进行分析之前,先把流程图贴上,其中的细节后面会通过代码辅助的方式一一讲解。

在理解tornado运行原理的过程中,我是通过写一个demo,然后在源码中到处打断点,然后调试的方式,一遍遍走,到最后慢慢地理解。顺便也把我的demo代码贴上吧(看过我之前的一篇译文的读者可能会发现,这个demo是从那儿仿照过来的)。

12345678910111213141516171819202122232425 import randomimport timefrom tornado import genfrom tornado.ioloop import IOLoop  @gen.coroutinedef get_url(url):    wait_time = random.randint(1, 4)    yield gen.sleep(wait_time)    print(\’URL {} took {}s to get!\’.format(url, wait_time))    raise gen.Return((url, wait_time))  @gen.coroutinedef outer_coroutine():    before = time.time()    coroutines = [get_url(url) for url in [\’URL1\’, \’URL2\’, \’URL3\’]]    result = yield coroutines    after = time.time()    print(result)    print(\’total time: {} seconds\’.format(after before)) if __name__ == \’__main__\’:    IOLoop.current().run_sync(outer_coroutine)

有兴趣的读者可以自己去执行一下玩玩,输出类似于这样:

12345 URL URL1 took 1s to get!URL URL2 took 2s to get!URL URL3 took 2s to get![(\’URL1\’, 1), (\’URL2\’, 2), (\’URL3\’, 2)]total time: 2.00353884697 seconds

Coroutine

起初我以为调用协程后,返回的是一个生成器对象,毕竟gen.coroutine装饰在一个函数或者生成器上。看了源码发现,其实每次调用一个协程,它在获取了生成器对象之后,同时又对它执行了next操作来获取生成器内部yield出来的值,这个可以是一个值,当然也可以是一个由内部协程嵌套调用返回的future对象。

123456789101112131415161718192021222324252627 # gen.pydef _make_coroutine_wrapper(func, replace_callback):    @functools.wraps(func)    def wrapper(*args, **kwargs):        future = TracebackFuture()        # 省略n行        try:            result = func(*args, **kwargs)        # 省略n个except        else:            if isinstance(result, types.GeneratorType):                try:                    orig_stack_contexts = stack_context._state.contexts                    yielded = next(result)                        # 如果func内部有yield关键字,result是一个生成器                    # 如果func内部又调用了其它协程,yielded将会是由嵌套协程返回的future对象                    # 省略n行                # 省略n个except                else:                    Runner(result, future, yielded)                try:                    return future                finally:                    future = None        future.set_result(result)        return future    return wrapper

Future

我觉得Future在tornado中是一个很奇妙的对象,它是一个穿梭于协程和调度器之间的信使。提供了回调函数注册(当异步事件完成后,调用注册的回调)、中间结果保存、嵌套协程唤醒父协程(通过Runner实现)等功能。Coroutine和Future是一一对应的,可以从上节gen.coroutine装饰器的实现中看到。每调用一个协程,表达式所返回的就是一个Future对象,它所表达的意义为:这个协程的内部各种异步逻辑执行完毕后,会把结果保存在这个Future中,同时调用这个Future中指定的回调函数,而future中的回调函数是什么时候被注册的呢?那就是当前——你通过调用协程,返回了这个future对象的时候:
我们看看demo代码中run_܀始下笔。如果贴上一段段源码,然后通过语言来描述各种流程,这种类型的文章网上也有不少,况且这样子的讲解对于读者来说可能会比较乏味。

我希望我对于源码分析的博文能够通过贴上更容易理解的图(当然也会有一些代码来辅助讲解),这样的分享对读者来说会更加容易读懂,也更有价值。对自己要求高了,反而更难下笔,在试图画图的过程中,发现其实有好多细节自己也没有琢磨透,导致在如何组织这幅流程图的问题上斟酌了好久,不过好在最后终于捯饬出了一张自己觉得还算及格的流程图,作完图的时候我感觉比起之前刚阅读完代码时候的理解又上了一个层次。

流程图

tornado执行协程的方式有很多,但协程内部的运行原理没什么区别,这篇文章以IOLoop中的run_sync函数作为入口进行介绍。在开始进行分析之前,先把流程图贴上,其中的细节后面会通过代码辅助的方式一一讲解。

在理解tornado运行原理的过程中,我是通过写一个demo,然后在源码中到处打断点,然后调试的方式,一遍遍走,到最后慢慢地理解。顺便也把我的demo代码贴上吧(看过我之前的一篇译文的读者可能会发现,这个demo是从那儿仿照过来的)。

12345678910111213141516171819202122232425 import randomimport timefrom tornado import genfrom tornado.ioloop import IOLoop  @gen.coroutinedef get_url(url):    wait_time = random.randint(1, 4)    yield gen.sleep(wait_time)    print(\’URL {} took {}s to get!\’.format(url, wait_time))    raise gen.Return((url, wait_time))  @gen.coroutinedef outer_coroutine():    before = time.time()    coroutines = [get_url(url) for url in [\’URL1\’, \’URL2\’, \’URL3\’]]    result = yield coroutines    after = time.time()    print(result)    print(\’total time: {} seconds\’.format(after before)) if __name__ == \’__main__\’:    IOLoop.current().run_sync(outer_coroutine)

有兴趣的读者可以自己去执行一下玩玩,输出类似于这样:

12345 URL URL1 took 1s to get!URL URL2 took 2s to get!URL URL3 took 2s to get![(\’URL1\’, 1), (\’URL2\’, 2), (\’URL3\’, 2)]total time: 2.00353884697 seconds

Coroutine

起初我以为调用协程后,返回的是一个生成器对象,毕竟gen.coroutine装饰在一个函数或者生成器上。看了源码发现,其实每次调用一个协程,它在获取了生成器对象之后,同时又对它执行了next操作来获取生成器内部yield出来的值,这个可以是一个值,当然也可以是一个由内部协程嵌套调用返回的future对象。

123456789101112131415161718192021222324252627 # gen.pydef _make_coroutine_wrapper(func, replace_callback):    @functools.wraps(func)    def wrapper(*args, **kwargs):        future = TracebackFuture()        # 省略n行        try:            result = func(*args, **kwargs)        # 省略n个except        else:            if isinstance(result, types.GeneratorType):                try:                    orig_stack_contexts = stack_context._state.contexts                    yielded = next(result)                        # 如果func内部有yield关键字,result是一个生成器                    # 如果func内部又调用了其它协程,yielded将会是由嵌套协程返回的future对象                    # 省略n行                # 省略n个except                else:                    Runner(result, future, yielded)                try:                    return future                finally:                    future = None        future.set_result(result)        return future    return wrapper

Future

我觉得Future在tornado中是一个很奇妙的对象,它是一个穿梭于协程和调度器之间的信使。提供了回调函数注册(当异步事件完成后,调用注册的回调)、中间结果保存、嵌套协程唤醒父协程(通过Runner实现)等功能。Coroutine和Future是一一对应的,可以从上节gen.coroutine装饰器的实现中看到。每调用一个协程,表达式所返回的就是一个Future对象,它所表达的意义为:

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...