浅谈Python库之asyncio
一、asyncio的介绍
asyncio
是 Python 中用于编写异步 I/O(输入 / 输出)代码的库。它是在 Python 3.4 版本中引入的,主要基于协程(coroutine)的概念构建。在传统的同步编程中,程序在执行 I/O 操作(如读取文件、网络请求)时会被阻塞,等待操作完成后才能继续执行下一行代码。而asyncio
允许程序在等待 I/O 操作时,将执行权交给其他任务,从而实现高效的并发执行。- 例如,在一个网络爬虫程序中,需要同时向多个网站发送请求获取数据。如果使用同步方式,每次请求都要等待前一个请求完成后才能开始,这会浪费大量时间在等待响应上。而使用
asyncio
,可以同时发起多个请求,当一个请求在等待服务器响应时,程序可以去处理其他请求,从而大大提高效率。
二、asyncio的特点
- 异步非阻塞 I/O
asyncio
的核心特点是实现了异步非阻塞的 I/O 操作。这意味着当一个 I/O 操作(如网络读取或写入、文件读取等)被发起后,程序不会等待这个操作完成,而是继续执行其他代码。当 I/O 操作完成时,通过事件循环(Event Loop)来通知相应的协程继续执行后续的操作。- 比如,在一个服务器应用程序中,当接收一个客户端的连接请求后,使用
asyncio
可以在等待客户端发送数据的过程中,同时处理其他已经连接的客户端的数据收发,而不是像传统的阻塞式 I/O 那样,一个客户端连接就会阻塞服务器对其他客户端的处理。
- 基于协程的并发模型
- 它以协程作为基本的并发单元。协程是一种轻量级的用户态线程,相比于传统的线程,协程的创建和切换成本更低。在
asyncio
中,协程通过async/await
语法来定义和暂停 / 恢复执行。协程可以在等待 I/O 操作或者其他协程完成时暂停自己的执行,将执行权交给事件循环,当条件满足时又可以被事件循环唤醒继续执行。 - 例如,有两个协程
coroutine1
和coroutine2
,coroutine1
在执行过程中发起了一个网络请求并等待响应,此时它可以通过await
暂停执行,事件循环会切换到coroutine2
执行,当网络请求的响应返回时,coroutine1
又可以被唤醒继续处理响应数据。
- 它以协程作为基本的并发单元。协程是一种轻量级的用户态线程,相比于传统的线程,协程的创建和切换成本更低。在
- 高效的事件循环机制
asyncio
有一个高效的事件循环来管理和调度协程的执行。事件循环会不断地检查各种 I/O 事件(如套接字可读、可写等)以及定时器事件等,根据事件的状态来决定唤醒哪些协程执行。这个事件循环是单线程的,但通过异步 I/O 和协程的配合,可以实现类似多线程的并发效果,并且避免了多线程带来的一些复杂性,如锁竞争、上下文切换开销等。- 例如,在一个简单的异步 TCP 服务器中,事件循环会持续监听客户端的连接请求,当有新的连接时,它会创建一个新的协程来处理这个连接,并且在整个过程中不断地协调各个协程的执行,确保服务器能够高效地处理多个客户端的通信。
三、asyncio的使用
定义协程
- 使用
async
关键字来定义协程函数。例如:
import asyncio
async def my_coroutine():
print("Coroutine started")
await asyncio.sleep(1) # 模拟一个异步操作,暂停1秒
print("Coroutine ended")
- 在这个例子中,
my_coroutine
是一个协程函数。await asyncio.sleep(1)
表示暂停协程的执行 1 秒,这个暂停期间,事件循环可以去执行其他协程。
运行协程
- 协程函数需要通过事件循环来运行。通常有两种方式:
- 方式一:直接运行协程(在 Python 3.7+)
asyncio.run(my_coroutine())
这种方式是最简单的,asyncio.run()
函数会创建一个事件循环,运行协程,然后关闭事件循环。
方式二:手动创建和使用事件循环(适用于更复杂的场景)
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(my_coroutine())
finally:
loop.close()
首先通过asyncio.get_event_loop()
获取事件循环对象,然后使用loop.run_until_complete()
来运行协程,直到协程完成。最后需要通过loop.close()
关闭事件循环。
多个协程并发运行
- 可以使用
asyncio.gather()
来同时运行多个协程。例如:
async def coroutine1():
print("Coroutine 1 started")
await asyncio.sleep(2)
print("Coroutine 1 ended")
async def coroutine2():
print("Coroutine 2 started")
await asyncio.sleep(1)
print("Coroutine 2 ended")
async def main():
await asyncio.gather(coroutine1(), coroutine2())
asyncio.run(main())
在这个例子中,coroutine1
和coroutine2
是两个协程,asyncio.gather()
函数会并发地运行这两个协程。main
协程通过await asyncio.gather()
来等待这两个协程都完成。整个程序的执行顺序是先启动coroutine1
和coroutine2
,然后coroutine2
会先完成(因为它的等待时间asyncio.sleep(1)
更短),最后coroutine1
完成。