前言
书接上回,前面我们聊了 Tornado 的两大核心优势,本篇我们深入底层技术细节,拆解异步与非阻塞 IO 的核心逻辑,搞懂为什么这些特性是 Tornado 实现高并发的关键。
一、核心概念拆解
要理解 Tornado 的性能优势,首先要搞清楚两个核心维度:同步 / 异步 、阻塞 / 非阻塞。
1. 什么是异步?
说到异步就不能不提同步,二者的核心区别是IO 结果的通知方式:
- 同步:应用线程自己主动等待、主动检查 IO 状态,任务完成前线程一直卡在检查逻辑上。
- 异步:把整个 IO 操作全部交给内核:发起 IO 调用之后,函数立刻返回,线程继续做别的事情;IO 完成后,系统主动通知线程处理结果。
2. 什么是非阻塞?
非阻塞的核心是调用是否立刻返回: 调用 IO 函数时,不管 IO 有没有就绪,函数都会立刻返回,不会卡住当前线程,线程可以继续执行其他逻辑。
二、通俗易懂:烧开水看懂四种 IO 模式
我们用一个生活化的场景来解释:
场景设定:你在厨房做事,烧一壶开水需要 8 分钟 「你本人」= 程序主线程 / 事件循环 「烧水」= 耗时 IO 任务
两个维度分开理解: ① 阻塞 / 非阻塞 :等待烧水时,你能不能走开干别的活; ② 同步(非异步)/ 异步:水开了是谁主动通知你。
1. 同步阻塞(最传统低效)
- 操作:把水壶放到燃气灶上,站在灶台前一动不动盯着烧水,什么家务都不干,必须等水烧开、关掉火之后,才去洗菜、擦桌子。
- 解读:阻塞 = 全程原地等待,不做其他工作;同步 = 全程自己盯着水开,没有水壶主动提醒,只能自己隔几秒看一次。
- 对应程序:传统同步 Web 服务,请求来了就死等数据库查询结束,期间不处理别的请求。
2. 同步非阻塞
- 操作:把水壶点火烧水,你转身去擦桌子,但是每隔 1 分钟就跑回灶台掀开壶盖看水有没有烧开,反复来回检查,直到水开再关火。
- 解读:非阻塞 = 没傻站原地等待,可以抽空干活;同步 = 没有提醒机制,必须自己反复轮询确认任务结果;看似干活多,但频繁来回检查会浪费精力。
- 对应程序:循环轮询接口状态,间隔主动查询任务是否完成。
3. 异步阻塞(现实很少用)
- 操作:给水壶装一个水开鸣笛报警器,你就站在灶台边原地不动,什么活都不干,静静等鸣笛响起再关火。
- 解读:异步 = 水开水壶主动鸣笛通知你,不用自己频繁查看;阻塞 = 你依然原地等候,等候期间不做其他事务,浪费了异步通知的优势。
4. 异步非阻塞(Tornado 主推最优模式)
- 操作:打开带鸣笛功能的水壶烧水,你直接去客厅拖地、择菜;水烧开后水壶自动鸣笛提醒你,听到声音再回厨房关火接水,等待的 8 分钟里你做完了好几件家务。
- 解读:非阻塞 = 等待烧水期间主线程可以处理别的事务;异步 = 任务结束主动推送通知,不用人工轮询;精力利用率最高。
- 对应 Tornado 原理:单线程事件循环高性能的核心 ------ 等待网络 IO 时去处理别的用户请求,IO 完成自动回调处理结果。
三、代码对比:同步请求 vs 异步请求
我们以 HTTP 请求为例,直观感受同步与异步代码的写法差异。
同步请求代码
from tornado.httpclient import HTTPClient
def synchronous_fetch(url):
http_client = HTTPClient()
response = http_client.fetch(url)
return response.body
同步模式下,fetch 方法会阻塞当前函数,直到请求返回结果,期间线程无法处理其他任务。
异步请求代码
from tornado.httpclient import AsyncHTTPClient
async def asynchronous_fetch(url):
http_client = AsyncHTTPClient()
response = await http_client.fetch(url)
return response.body
异步模式下,await 会挂起当前协程,事件循环可以转去处理其他请求;请求完成后,再回到当前位置继续执行后续逻辑。
结语
以上就是异步与非阻塞 IO 的核心原理,也是 Tornado 能够支撑高并发场景的底层逻辑。小伙伴们清楚了吗?感兴趣的快去动手试试吧!
风险提示:本文仅做风险提示,不做投资教学,投资有风险,入市需谨慎。