什么是异步编程?
Python asyncio异步编程:从入门到实战指南
在现代软件开发中,处理大量并发任务已成为常态。无论是构建高性能 Web 服务、爬取海量网页,还是实时处理传感器数据,传统同步编程模型往往难以满足性能需求。Python 的 asyncio 模块为此提供了一套优雅的异步编程解决方案,它通过协程(coroutine)和事件循环(event loop)机制,让开发者能以接近同步代码的写法,实现高效的并发执行。本文将带你从基础概念出发,逐步掌握 asyncio 的核心用法,并通过实战案例深入理解其应用场景。
什么是异步编程?
异步编程是一种编程范式,允许程序在等待某些操作(如 I/O、网络请求)完成时,不阻塞主线程,而是继续执行其他任务。与多线程或多进程不同,异步编程通常在单线程内通过“协作式多任务”实现并发,资源开销更小,上下文切换成本更低。
在 Python 中,asyncio 是官方提供的异步 I/O 框架,自 3.4 版本引入,3.5 起支持 async/await 语法,极大简化了异步代码的编写。

基础概念:协程与事件循环
协程是异步编程的核心。你可以把它理解为一种可以“暂停”和“恢复”的函数。在 Python 中,使用 async def 定义协程函数:
import asyncio
async def say_hello():
print("Hello")
await asyncio.sleep(1) # 模拟异步操作
print("World")
# 运行协程
asyncio.run(say_hello())
注意:直接调用 say_hello() 不会执行函数体,而是返回一个协程对象。必须通过 asyncio.run() 或在已有事件循环中调度才能真正运行。
事件循环(Event Loop)是 asyncio 的调度中心,负责管理所有协程的执行、暂停与恢复。asyncio.run() 会自动创建并启动一个事件循环,运行传入的协程,完成后关闭循环。
并发执行多个任务
单个协程只能顺序执行,真正的并发需要同时调度多个协程。asyncio 提供了多种方式实现并发:
使用 asyncio.create_task()
import asyncio
async def fetch_data(task_id, delay):
print(f"Task {task_id} started")
await asyncio.sleep(delay)
print(f"Task {task_id} finished")
return f"Data from task {task_id}"
async def main():
# 创建多个任务
task1 = asyncio.create_task(fetch_data(1, 2))
task2 = asyncio.create_task(fetch_data(2, 1))
# 等待所有任务完成
result1 = await task1
result2 = await task2
print(result1, result2)
asyncio.run(main())
输出顺序表明两个任务是并发执行的:先打印“Task 1 started”,再“Task 2 started”,约1秒后“Task 2 finished”,再1秒后“Task 1 finished”。
使用 asyncio.gather()
当需要同时启动多个协程并收集结果时,gather() 更简洁:
import asyncio
async def main():
results = await asyncio.gather(
fetch_data(1, 2),
fetch_data(2, 1),
fetch_data(3, 1.5)
)
print("All results:", results)
asyncio.run(main())
gather() 会并行执行所有传入的协程,并按参数顺序返回结果列表。
实战:异步 HTTP 请求
假设我们需要从多个 URL 获取数据。使用同步方式逐个请求效率低下,而异步方式可显著提升速度。
首先安装 aiohttp(标准库无异步 HTTP 客户端):
pip install aiohttp
然后编写异步爬虫:
import asyncio
import aiohttp
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
'https://httpbin.org/delay/1',
'https://httpbin.org/delay/2',
'https://httpbin.org/delay/1'
]
async with aiohttp.ClientSession() as session:
tasks = [fetch_url(session, url) for url in urls]
responses = await asyncio.gather(*tasks)
print(f"Fetched {len(responses)} pages.")
# 注意:实际运行需确保网络环境允许访问
# asyncio.run(main())
此代码在单个会话中并发发起多个请求,总耗时接近最慢请求的时间(约2秒),而非各请求时间之和(约4秒)。
异步上下文管理器与生成器
asyncio 支持异步上下文管理器(async with)和异步生成器(async for),用于处理需要异步初始化/清理的资源或流式数据。
例如,异步文件读取(需第三方库如 aiofiles):
# 示例:伪代码展示异步文件操作结构
# import aiofiles
#
# async def read_file():
# async with aiofiles.open('data.txt', 'r') as f:
# content = await f.read()
# return content
异步生成器可用于逐行处理大文件或实时数据流:
async def async_range(n):
for i in range(n):
await asyncio.sleep(0.1) # 模拟异步延迟
yield i
async def main():
async for value in async_range(3):
print(value)
asyncio.run(main())
常见陷阱与最佳实践
-
不要在协程中使用阻塞函数
如time.sleep()、requests.get()等会阻塞整个事件循环。应使用asyncio.sleep()或异步替代库(如aiohttp)。 -
避免“假异步”
即使使用async/await,若内部调用的是同步 I/O,仍无法获得并发优势。 -
合理控制并发数量
同时发起成千上万个请求可能导致服务器拒绝或本地资源耗尽。可使用asyncio.Semaphore限制并发数:
import asyncio
semaphore = asyncio.Semaphore(5) # 最多5个并发
async def limited_fetch(url):
async with semaphore:
# 执行请求
await asyncio.sleep(0.1)
return f"Done {url}"
async def main():
urls = [f"url_{i}" for i in range(20)]
tasks = [limited_fetch(url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
总结与建议
Python 的 asyncio 为 I/O 密集型任务提供了高效、简洁的并发解决方案。通过 async/await 语法,开发者能以清晰的逻辑编写高性能异步程序。然而,异步编程并非万能——它适用于高并发 I/O 场景(如 Web 服务、爬虫、数据库访问),但对 CPU 密集型任务帮助有限,此时应考虑多进程或优化算法。
建议初学者从简单示例入手,理解事件循环与协程的协作机制;在项目中逐步引入异步模式,优先替换明显的 I/O 瓶颈;同时注意工具链选择(如使用 aiohttp 而非 requests)。掌握 asyncio 不仅能提升程序性能,更是迈向现代 Python 开发的重要一步。

