
在现代编程中,异步编程(Asynchronous Programming) 是提升程序并发性能的关键技术之一。
无论是高并发的 Web 服务、网络爬虫,还是 I/O 密集型任务,异步编程都能极大提升运行效率。
本文将带你从零开始理解 Python 的异步编程模型,包括:
- 什么是异步?
- 为什么要使用异步?
- Python 的
async/await语法 asyncio的使用- 常见陷阱与最佳实践
文章目录
一、什么是异步(Asynchronous)?
举个例子
假设你要煮饭,同时还要洗衣服。
- 同步(Synchronous):煮饭 → 等待煮饭结束 → 洗衣服
- 异步(Asynchronous):煮饭开始 → 同时洗衣服 → 两件事同时进行
在计算机中,这就类似于:
当一个任务在等待 I/O(例如文件读取、网络请求)时,CPU 不再闲着,而是去执行其他任务。
二、为什么要使用异步?
Python 中的异步主要为了解决 I/O 密集型任务效率低 的问题,比如:
- 网络请求(如爬虫、API 调用)
- 文件或数据库读写
- Web 服务器的高并发处理
在这些场景中,程序的大部分时间都在等待,而不是计算。
异步编程能在等待期间调度其他任务,从而实现“并发执行”。
三、同步 vs 异步 示例对比
同步写法
import time
def task(name):
print(f"{name} 开始")
time.sleep(2)
print(f"{name} 结束")
def main():
task("任务A")
task("任务B")
main()
输出:
任务A 开始
任务A 结束
任务B 开始
任务B 结束
总耗时约 4 秒。
异步写法
import asyncio
async def task(name):
print(f"{name} 开始")
await asyncio.sleep(2)
print(f"{name} 结束")
async def main():
await asyncio.gather(task("任务A"), task("任务B"))
asyncio.run(main())
输出:
任务A 开始
任务B 开始
任务A 结束
任务B 结束
总耗时约 2 秒,性能提升一倍 !
四、Python 异步的核心语法
Python 3.5 引入了两个关键字:
async—— 定义异步函数await—— 等待异步任务的完成
1. async:定义协程函数(coroutine)
async def hello():
print("Hello, async world!")
这个函数不会立即执行,而是返回一个 协程对象(coroutine object)。
print(hello())
## 输出结果:<coroutine object hello at 0x7f67e693fa40>
2. await:挂起等待另一个异步操作完成
await 只能在 async 函数中使用,它的作用是:
暂停当前协程,等被等待的异步任务执行完再继续。
示例:
import asyncio
async def hello():
print("Start...")
await asyncio.sleep(2)
print("End!")
asyncio.run(hello())
输出:
Start...
(2 秒后)
End!
五、asyncio 核心概念
Python 标准库提供了异步框架 asyncio,用于管理和调度协程。
核心组成:
| 组件 | 说明 |
|---|---|
asyncio.run() | 启动事件循环 |
asyncio.create_task() | 创建任务对象(task) |
await | 挂起当前协程等待结果 |
asyncio.gather() | 并发执行多个协程 |
示例:并发执行多个任务
import asyncio
async def work(name, seconds):
print(f"{name} 开始工作...")
await asyncio.sleep(seconds)
print(f"{name} 完成!")
async def main():
task1 = asyncio.create_task(work("任务1", 2))
task2 = asyncio.create_task(work("任务2", 3))
await task1
await task2
asyncio.run(main())
输出:
任务1 开始工作...
任务2 开始工作...
任务1 完成!
任务2 完成!
六、asyncio.gather() 同时执行多个协程
如果希望简化写法,可以用 asyncio.gather() 一次性启动多个任务:
import asyncio
async def download(name, delay):
print(f"{name} 开始下载...")
await asyncio.sleep(delay)
print(f"{name} 下载完成!")
async def main():
await asyncio.gather(
download("文件A", 2),
download("文件B", 3),
download("文件C", 1)
)
asyncio.run(main())
输出(近似):
文件A 开始下载...
文件B 开始下载...
文件C 开始下载...
文件C 下载完成!
文件A 下载完成!
文件B 下载完成!
总耗时约 3 秒(最长的任务)。
七、异步与多线程、多进程的区别
| 特性 | 异步(asyncio) | 多线程(threading) | 多进程(multiprocessing) |
|---|---|---|---|
| 核心思想 | 单线程事件循环 | 多线程并行 | 多进程并行 |
| 适用场景 | I/O 密集型 | I/O 密集型 | CPU 密集型 |
| 是否占用多个 CPU | 否 | 否(受 GIL 限制) | ✅ 是 |
| 上下文切换 | 轻量(协程) | 较重 | 最重 |
| 代码复杂度 | 中等 | 较高 | 较高 |
总结:
- 异步(
asyncio)适合 I/O 密集型任务 - 多进程适合 CPU 密集型任务
八、常见异步陷阱
❌ 1. 在普通函数中使用 await
await asyncio.sleep(1)
# SyntaxError: 'await' outside async function
✅ 正确做法:
async def main():
await asyncio.sleep(1)
❌ 2. 忘记使用 await
async def main():
asyncio.sleep(1)
print("Done!")
这段代码不会等待 sleep 完成,而是直接打印。
✅ 应写为:
await asyncio.sleep(1)
❌ 3. 忽略事件循环(loop)
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在 Python 3.7+ 中更推荐使用:
asyncio.run(main())
九、实战:异步爬虫示例
import asyncio
import aiohttp # 异步HTTP库
urls = [
"https://example.com",
"https://www.python.org",
"https://pytorch.org"
]
async def fetch(session, url):
async with session.get(url) as response:
print(f"{url} 状态码:{response.status}")
async def main():
async with aiohttp.ClientSession() as session:
tasks = [fetch(session, url) for url in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
输出:
https://example.com 状态码:200
https://www.python.org 状态码:200
https://pytorch.org 状态码:200
仅需几百毫秒即可完成多个请求!
十、最佳实践总结
✅ 写异步函数
async def func(): ...
✅ 调用异步函数
await func()
✅ 运行异步程序
asyncio.run(main())
✅ 并发执行多个任务
await asyncio.gather(task1(), task2(), task3())
✅ I/O 密集型推荐异步,CPU 密集型推荐多进程
如果想更深一步了解 Python 的异步操作,请参考深入理解 Python asyncio:事件循环与 Task 调度机制
浙公网安备 33010602011771号