在现代编程中,并发是提高程序效率的关键技术之一,它允许程序同时执行多个任务,充分利用系统资源。
本文将深入探讨 Python
中的async/await
机制,从并发编程基础讲起,逐步剖析其工作原理和实现方式。
1. 并发编程基础
计算机程序的执行方式主要有两种:顺序执行 和并发执行。
顺序执行是按代码顺序逐条运行,而并发执行则允许同时运行多个任务。
并发 又分为并发 (concurrency
)和并行 (parallelism
),并发 是指多个任务同时进行,但不一定同时运行;并行则是多个任务同时运行,通常需要多核处理器支持。
假设有3个任务,每个任务有若干步骤,每个任务情况如下:

顺序执行的情况如下:

并发 (concurrency
)执行的情况如下,三个任务交替执行,感觉像是同时在运行。

并行 (parallelism
)执行的情况如下,三个任务同时运行。

不同的编程语言对并发编程的支持各有不同。
Python
通过 GIL
(全局解释器锁)限制了多线程的并行能力,但提供了多种并发编程方式,如线程、多进程、事件循环等,这些方式各有优缺点,适用于不同的场景。
2. async/await 语法
从Python 3.5
开始,引入了一种新的异步编程语法async/await
,用于简化异步操作的编写。
它基于生成器和事件循环,使得异步代码更加直观和易于理解。
其中,async
关键字用于定义一个异步函数。
当一个函数被定义为async
时,它会返回一个协程对象。
协程 是一种特殊的函数,它可以在执行过程中暂停和恢复,非常适合处理 I/O
密集型任务。
比如:
python
async def fetch_data():
await asyncio.sleep(2) # 模拟异步操作
return "Data fetched"
调用async
函数时,不会立即执行函数体,而是返回一个协程对象。要运行协程,需要将其提交到事件循环中。
await
关键字用于暂停当前协程的执行,等待一个可等待对象(如协程、Future 或 Task)完成。
await
后面的表达式必须是一个可等待对象,否则会抛出TypeError
。
比如:
python
async def main():
result = await fetch_data() # 暂停 main,直到 fetch_data 完成
print(result)
当遇到await
时,当前协程会暂停执行,并将控制权交还给事件循环。
事件循环会继续执行其他任务,直到await
的异步操作完成。
2.1. 执行流程
async/await
的执行流程一般分为3步:
- 协程的启动 :调用async函数会返回一个协程对象,要执行这个协程,需要将其提交给事件循环,比如通过
asyncio.run()
或loop.run_until_complete()
方法。 - 暂停与恢复 :当协程遇到
await
时,它会暂停并将控制权交给事件循环。事件循环接着执行其他任务,直到await
的操作完成,然后恢复该协程的执行。 - 异常处理 :
async/await
支持在协程中使用try/except
捕获异常,这使得错误处理更加直观和方便。
python
async def risky_task():
raise ValueError("Something went wrong")
async def main():
try:
await risky_task()
except ValueError as e:
print(f"Caught an exception: {e}")
2.2. async/await的优势
其实不用async/await
的语法,也可以实现异步,Python
引入这个语法的主要是因为可以带来一下的好处:
- 代码简洁易读 :
async/await
使得异步代码更加接近同步代码,避免了回调地狱和复杂的链式调用 - 错误处理方便 : 使用
try/except
可以直接捕获协程中的异常,而无需在每个异步操作中处理错误 - 性能优化 :
async/await
基于事件循环和协程,避免了线程切换的开销,适合处理大量I/O
密集型任务
2.3. 基于async/await的服务器实现
以下是使用async/await
和asyncio
实现的 TCP Echo
服务器代码。
与async/await
之前的Python
语法相比,代码更加简洁易读。
python
import asyncio
async def echo_handler(reader, writer):
addr = writer.get_extra_info("peername")
print(f"Connected from {addr}")
while True:
data = await reader.read(1024) # 非阻塞读取数据
if not data:
break
writer.write(data) # 非阻塞写入数据
await writer.drain() # 等待数据发送完成
writer.close()
print(f"Connection closed from {addr}")
async def run_server():
server = await asyncio.start_server(echo_handler, "127.0.0.1", 8080)
async with server:
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(run_server())
3. asyncio 库
async/await
只是Python
语言层面的特性,而asyncio
是Python
的标准异步编程库,提供了一套完整的工具和接口,用于构建异步应用程序。
asyncio
的核心功能围绕事件循环展开,通过事件循环,asyncio
能够高效地管理并发任务,实现 I/O
操作的异步执行。
它的主要功能和组件包括:
3.1. 事件循环(Event Loop)
事件循环 是asyncio
的核心,它负责调度和管理异步任务。
事件循环的主要职责包括:
- 任务调度:事件循环会跟踪所有注册的任务,并根据任务的状态(如等待 I/O 操作或定时器到期)调度它们的执行。
- I/O 多路复用 :通过底层的 I/O 多路复用机制(如
select
、epoll
或kqueue
),事件循环能够高效地处理多个并发的 I/O 操作。 - 异步任务的生命周期管理:事件循环负责启动、暂停、恢复和取消异步任务。
在 Python
中,可以通过asyncio.get_event_loop()
获取当前的事件循环,或者使用asyncio.run()
启动一个新的事件循环。
3.2. 协程(Coroutines)
协程是asyncio
的基本执行单元,它通过async
和await
关键字定义。
协程可以暂停和恢复执行,非常适合处理 I/O 密集型任务。
以下是一个简单的协程示例:
python
async def fetch_data():
await asyncio.sleep(2) # 模拟异步 I/O 操作
return "Data fetched"
async def main():
result = await fetch_data()
print(result)
asyncio.run(main())
在asyncio
中,协程通过事件循环进行调度。
当遇到await
时,当前协程会暂停执行,事件循环会继续处理其他任务,直到await
的异步操作完成。
3.3. 任务(Tasks)
任务 是协程的封装,它允许对协程进行更细粒度的控制,任务可以被取消、等待或加入到任务组中。
以下是一个使用任务的示例:
python
async def worker(name, delay):
await asyncio.sleep(delay)
print(f"Worker {name} completed")
async def main():
task1 = asyncio.create_task(worker("A", 2))
task2 = asyncio.create_task(worker("B", 3))
await task1
await task2
asyncio.run(main())
在asyncio
中,任务 是通过asyncio.create_task()
创建的。任务可以被加入到任务组中,以便并行执行多个任务。
3.4. Future 对象
Future
是一个表示异步操作结果的对象。
它通常用于低层次的异步编程,例如在回调函数中处理异步操作的结果。
Future
对象可以通过set_result()
或set_exception()
设置结果或异常。
python
async def main():
loop = asyncio.get_running_loop()
future = loop.create_future()
loop.call_soon(future.set_result, "Hello, Future!")
result = await future
print(result)
asyncio.run(main())
在asyncio
中,Future
对象通常用于与底层事件循环交互,而协程和任务则更常用于高层的异步编程。
3.5. 回调管理
asyncio
提供了强大的回调管理功能,允许在特定事件发生时执行回调函数。
例如,可以通过loop.call_soon()
或loop.call_later()
将回调函数加入到事件循环中。
python
async def main():
loop = asyncio.get_running_loop()
loop.call_soon(lambda: print("Callback executed immediately"))
loop.call_later(2, lambda: print("Callback executed after 2 seconds"))
await asyncio.sleep(3) # 等待足够的时间以触发回调
asyncio.run(main())
回调管理 是asyncio
的一个重要特性,它允许开发者在事件循环中插入自定义的逻辑。
3.6. 优势与局限性
asyncio
的优势非常明显:
- 高性能 :
asyncio
基于单线程事件循环,避免了线程切换的开销,适合处理大量并发的I/O
密集型任务 - 简洁易读 :
async/await
语法使得异步代码更加接近同步代码,易于理解和维护 - 强大的功能 :
asyncio
提供了丰富的功能,包括任务调度、回调管理、异步网络通信等
不过,它的局限性也不能忽视:
- CPU密集型任务的限制 :由于
asyncio
基于单线程 事件循环,它不适合处理 CPU 密集型任务。对于这类任务,建议使用多进程或其他并发模型 - 兼容性问题 :
asyncio
的某些功能可能与传统的同步代码不兼容,需要开发者进行适当的适配 - 调试复杂性 :虽然
asyncio
提供了强大的异步编程能力,但调试异步代码可能比调试同步代码更复杂
4. 总结
async/await
模式是Python
中一种高效的并发编程方式。
它结合了生成器和事件循环的优点,提供了简洁易读的代码。
然而,它也有缺点,例如对 CPU-bound
任务支持不足,除了async/await
,Python
还有其他并发编程模型,如多进程、线程池等。
此外,也介绍了asyncio
库,它也在不断改进和扩展。
例如,Python 3.10
引入了asyncio.run()
的改进版本,使得异步程序的启动更加简洁。
并且asyncio
也在不断优化其性能和兼容性,以更好地支持现代异步应用的开发。