Python 和 Node.js 的异步并发实现机制有显著不同,尽管它们都提供了 async/await 语法。
Python 的异步并发实现
Python 的异步并发基于事件循环(Event Loop) 和 协程(Coroutine)。
核心组件:
- 事件循环(Event Loop):作为中央执行器,负责调度和执行协程,并处理 IO 事件。
- 协程(Coroutine):使用
async def定义的函数,返回一个协程对象。协程可以在事件循环中挂起和恢复。 - 任务(Task):对协程的进一步封装,表示一个可调度的单元。
工作流程:
- 事件循环管理一个任务队列,循环执行就绪的任务。
- 当任务遇到 IO 操作时,它会挂起自己(通过 await),并将控制权交还给事件循环。
- 事件循环会监视 IO 操作(通过 selector 模块),当 IO 完成时,将对应的任务标记为就绪,等待下一次循环执行。
示例代码:
import asyncio
async def fetch_data():
print(" 开始获取数据 ")
await asyncio.sleep(2) # 模拟 IO 操作,挂起当前协程
print(" 数据获取完成 ")
return " 数据 "
async def main():
# 创建多个任务,这些任务在事件循环中并发执行
task1 = asyncio.create_task(fetch_data())
task2 = asyncio.create_task(fetch_data())
# 等待两个任务完成
await task1
await task2
asyncio.run(main())并发模型:
- 任务(Task):Python 的 asyncio 通常是单线程的,通过事件循环在单个线程中实现并发。
- 单线程:任务通过 await 主动让出控制权,实现协作式多任务。
Node.js 的异步并发实现
Node.js 的异步并发基于事件循环(Event Loop) 和 回调函数,后来引入了 Promise 和 async/await。
核心组件:
- 事件循环(Event Loop):由 libuv 库提供,负责执行延迟任务和 IO 事件回调。
- 回调函数(Callback):异步操作完成后执行的函数。
- Promise:表示异步操作最终完成或失败的对象。
- async/await:基于 Promise 的语法糖,使异步代码看起来像同步代码。
工作流程:
- Node.js 启动时,初始化事件循环,然后执行主线程代码。
- 遇到异步操作(如文件读写、网络请求),将其交给底层线程池(由 libuv 管理)执行,主线程继续执行后续代码。
- 异步操作完成后,将回调函数放入事件循环的任务队列。
- 事件循环在适当的时候(主线程空闲时)从任务队列中取出回调函数并执行。
示例代码:
async function fetchData() {
console.log(" 开始获取数据 ");
// 模拟 IO 操作,返回一个 Promise
await new Promise(resolve => setTimeout(resolve, 2000));
console.log(" 数据获取完成 ");
return " 数据 ";
}
async function main() {
// 创建多个异步任务,它们会并发执行
const task1 = fetchData();
const task2 = fetchData();
// 等待两个任务完成
await task1;
await task2;
}
main();并发模型:
- 单线程 + 线程池:Node.js 的主线程是单线程,但 IO 操作由底层线程池(libuv)执行,从而实现非阻塞 IO。
- 事件驱动:通过事件循环处理 IO 事件回调,实现高并发。
两者对比
| 特性 | Python asyncio | Node.js |
|---|---|---|
| 底层实现 | asyncio(内置) | libuv(C 语言编写) |
| 并发模型 | 单线程(协程) | 单线程(主线程)+ 线程池(IO 操作) |
| 调度方式 | 协作式多任务(任务主动让出) | 事件驱动(回调函数) |
| IO 支持 | 只能异步化支持 asyncio 的库(如 aiohttp) | 几乎所有 IO 操作都是异步的 |
| CPU 密集型任务 | 会阻塞事件循环(需要多进程处理) | 会阻塞主线程(需要子进程或 Worker Threads) |
| 生态系统 | 异步库逐渐丰富 | 从开始就围绕异步构建 |
注意事项
Python:
- 异步代码必须在异步函数中运行(使用 await)。
- 标准库中很多模块不支持异步,需要使用异步版本(如 aiohttp 代替 requests)。
- CPU 密集型任务会阻塞事件循环,需要使用多进程(如
asyncio.run_in_executor)。
Node.js:
- 几乎所有 IO 操作都是异步的,但 CPU 密集型操作会阻塞主线程。
- 可以使用 Worker Threads 来处理 CPU 密集型任务。
- 错误处理需要小心,未捕获的 Promise 拒绝可能会导致进程退出。
总结
两者都利用事件循环实现异步并发,但实现细节和生态系统有较大差异。Python 的 asyncio 更侧重于单线程内的协程并发,而 Node.js 则从设计之初就围绕异步 IO 构建,拥有更成熟的异步生态系统。