前言
在上一篇文章中,我们系统介绍了 Tornado 框架的核心优势,以及其异步非阻塞的底层特性。作为异步编程的核心载体,协程是 Tornado 开发中必须掌握的核心能力,同时也是量化交易系统中实现高并发策略的关键工具。
本篇将深入讲解 Tornado 中协程的定义、分类、与 Python 原生协程的差异,以及底层运行原理,帮助大家彻底理解协程的本质。
一、什么是协程
协程是 Tornado 框架中编写异步代码的官方推荐方式。与传统的回调链异步写法不同,协程借助 Python 的 await 关键字实现执行的暂停与恢复,让异步代码拥有接近同步代码的编写体验。
补充说明:在 gevent 等框架中,协作式轻量线程也常被称为协程;但在 Tornado 体系中,协程均采用显式上下文切换,以异步函数的形式进行调用。
1.1 协程的核心优势
- 开发效率高:代码写法与同步代码高度相似,无需嵌套回调,逻辑直观易懂。
- 资源开销低:协程本质是用户态的上下文切换,没有线程切换的内核态开销,并发性能更优。
- 可维护性强:上下文切换位置明确,减少了并发场景下的竞态隐患,业务逻辑更易排查与调试。
1.2 基础代码示例
下面是一个原生协程实现的异步 HTTP 请求示例:
bash
async def fetch_coroutine(url):
http_client = AsyncHTTPClient()
response = await http_client.fetch(url)
return response.body
可以看到,除了 async 和 await 两个关键字,整体写法与同步函数几乎一致,可读性非常高。
二、Tornado 协程与 Python 原生协程的区别
Python 3.5 正式引入 async / await 关键字,由此定义的协程被称为原生协程 。 而在更早的 Python 版本中,Tornado 提供了 tornado.gen.coroutine 装饰器,通过 yield 实现异步,这类写法被称为装饰器式协程(也叫基于 yield 的旧式协程)。
2.1 选型建议
- 若运行环境为 Python 3.5+,优先使用原生协程,这也是 Tornado 官方文档的标准写法。
- 仅当需要兼容 Python 2 等低版本环境时,再选择装饰器式协程。
2.2 写法快速对照
两种协程的语法可以很方便地互相转换,核心对应关系如下:
| 原生协程(Native) | 装饰器式协程(Decorated) |
|---|---|
async def 关键字定义函数 |
普通函数声明 + @gen.coroutine 装饰器 |
await 等待所有异步函数 |
yield 等待所有异步函数 |
普通 return 返回结果 |
通过 raise gen.Return(b) 返回结果 |
对应代码示例对比:
原生协程写法
bash
async def a():
b = await c()
return b
装饰器式协程写法
bash
@gen.coroutine
def a():
b = yield c()
raise gen.Return(b)
2.3 核心差异详解
(1)原生协程的特点
- 执行性能更优:原生协程由 Python 解释器原生支持,运行速度通常更快。
- 语法更丰富 :支持
async for、async with等高级语法,能大幅简化资源管理、遍历等场景的代码。 - 执行特性 :原生协程对象创建后,若不通过
await触发,函数体完全不会执行;而装饰器式协程在调用后就会在后台启动执行。
注意:无论是哪类协程,都必须通过
await或yield等待,其内部抛出的异常才能被外层正常捕获处理。
(2)装饰器式协程的特点
- 兼容
concurrent.futures:可以直接通过yield接收executor.submit的执行结果;原生协程需要借助IOLoop.run_in_executor实现同等能力。 - 批量等待更简洁 :直接
yield列表、字典即可同时等待多个异步对象;原生协程需要借助tornado.gen.multi完成批量等待。 - 第三方框架适配 :支持通过注册转换函数对接 Twisted 等第三方异步框架;原生协程需调用
tornado.gen.convert_yielded实现。 - 返回值类型 :装饰器式协程调用后固定返回
Future对象;原生协程返回可等待对象,不属于严格的Future类型,但在 Tornado 框架内二者大多可以互换使用。
三、协程的运行原理
我们以装饰器式协程为例讲解运行原理,原生协程的设计思路与之高度相似,只是因为需要和 Python 运行时深度适配,内部实现逻辑更复杂。
3.1 生成器是协程的基础
包含 yield 关键字的函数本质是生成器 。生成器的核心特性是:调用函数时不会立即执行完整代码,而是先返回一个生成器对象,每次驱动执行才会运行到下一个 yield 处暂停。
这种 "可暂停、可恢复" 的特性,正是协程实现异步的底层基础。
3.2 @gen.coroutine 装饰器的核心逻辑
@gen.coroutine 装饰器主要承担两层交互:
- 对内 :通过
yield表达式与内部生成器通信,驱动生成器分步执行。 - 对外 :向协程的调用者返回一个
Future对象,统一异步调用接口。
3.3 完整执行流程
装饰器内部的核心执行循环可以简化为以下步骤:
- 调用被装饰的函数,得到生成器对象,同时创建一个对外的 Future 对象。
- 驱动生成器执行,获取
yield抛出的 Future 对象(即异步任务)。 - 以非阻塞方式等待该 Future 任务执行完成。
- 任务完成后,将 Future 中的结果 / 异常回传给生成器,作为
yield表达式的返回值。 - 循环上述过程,直到生成器执行结束,最终将结果设置到对外的 Future 中。
在日常开发中,绝大多数异步业务代码都不需要直接操作 Future 类,只需要将异步函数返回的对象放入 yield / await 中等待即可。
总结
协程是 Tornado 异步编程的核心,理解两种协程写法的差异与底层运行原理,是写出高效、稳定异步代码的基础。
- 新项目优先选择原生协程写法,兼顾性能与语法特性。
- 协程的本质是基于 "可暂停的函数" 实现的用户态并发,避免了线程的高额开销。
- 日常开发中无需过度关注底层 Future 细节,专注业务逻辑即可。
在下一篇内容中,我们将继续讲解协程的实战用法与常见场景封装。