异步 IO asyncio
Python 并发和并行方案
在 Python 世界有 3 种并发和并行方案,如下:
- 多线程 (threading)
- 多进程 (multiprocessing)
- 异步 IO (asyncio)
这些方案是为了解决不同特点的性能瓶颈。性能问题主要有 2 种:
- CPU 密集型 (CPU-bound)。这也就是指计算密集型任务,它的特点事需要要进行大量的计算。例如 Python 内置对象的各种方法的执行,科学计算,视频转码等等。
- I/O 密集型 (I/O-bound)。凡是涉及到网络、内存访问、磁盘 I/O 等的任务都是 IO 密集型任务,这类任务的特点是 CPU 消耗很少,任务的大部分时间都在等待 I/O 操作完成。例如数据库连接、Web 服务、文件读写等等。
这三个方案中对于 CPU 密集型的任务,优化方案只有一种,就是使用多进程充分利用多核 CPU 一起完成任务,达到提速的目的。而对于 I/O 密集型的任务,则这三种方案都可以。
asyncio 是什么?
asyncio 是 Python 用于编写单线程并发代码使用的库,使用 async/await 语法。它被用于编写、执行异步操作和协程,从而允许 IO 绑定和高级别的结构化网络代码,尤其是在高性能网络服务器和客户端应用中。以下是关于 asyncio 的一些关键点,以及如何与 async 和 await 关键字结合使用的例子。
基本概念
- 事件循环:asyncio 的核心组件,用于执行异步操作和协程。事件循环负责管理和分发事件到不同的任务。
- 任务:事件循环中的一个可等待对象,用于封装协程的执行。
- 协程:通过 async def 定义的一个特殊函数,它是 asyncio 用于声明异步操作的方法。
- async 用于声明一个协程。这样的函数调用不会立即执行;相反,它会返回一个协程对象,这个对象可以被 await、封装成任务或者通过其他方式调度。
- await 用于等待一个可等待对象(如协程或任务),并且暂停当前协程的执行,直到等待的对象完成。这允许其他协 程运行。
asyncio.run 的原理
asyncio.run(coro, *, debug=False) 是 Python 3.7+ 引入的高级函数,用于执行一个异步程序。
它接受一个协程 coro,创建一个新的事件循环,运行传入的协程,直到完成,然后关闭事件循环。如果在调用 asyncio.run() 之前事件循环已经在运行,它会抛出一个异常。这个函数主要是为了简化异步程序的启动。
import asyncio
async def main():
print('Hello')
await asyncio.sleep(1)
print('world')
# Python 3.7+
asyncio.run(main())
基本异步函数使用
同时运行多个协程
import asyncio
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
task1 = asyncio.create_task(say_after(1, 'hello'))
task2 = asyncio.create_task(say_after(2, 'world'))
# 等待两个任务完成
await task1
await task2
asyncio.run(main())
结合使用 asyncio.gather
asyncio.gather用于并发运行多个可等待对象。
import asyncio
async def say_after(delay, what):
await asyncio.sleep(delay)
print(what)
async def main():
await asyncio.gather(
say_after(1, 'hello'),
say_after(2, 'world'),
)
asyncio.run(main())
在非异步函数中执行异步函数
有几种方法可以在非异步函数中执行异步函数,但是最常见和推荐的方式是使用 asyncio.run()。
import asyncio
async def async_func():
print("异步函数开始")
await asyncio.sleep(1)
print("异步函数结束")
def normal_func():
asyncio.run(async_func())
normal_func()
如果你已经在一个 asyncio.run(main()) 调用的上下文中并且想要从非异步函数中执行异步代码,你可以使用 asyncio.create_task() 或者 asyncio.get_event_loop().run_until_complete()