引言:阻塞的困境
想象一下,你的 Python 程序需要从多个网站抓取数据,或者同时处理成百上千个网络连接(比如一个聊天服务器)。如果你使用传统的同步编程方式,代码可能是这样的:
import requests
def fetch_data(url):
response = requests.get(url) # 阻塞点:程序在这里等待网络响应,啥也不干!
return response.text
urls = ['https://site1.com', 'https://site2.com', 'https://site3.com']
for url in urls:
data = fetch_data(url)
process_data(data) # 处理数据
这段代码的问题在于 requests.get(url)
是阻塞的。当程序执行到这行时,它会一直等待,直到收到网站的响应。在此期间,整个程序(或者至少当前线程)被“冻结”,CPU 资源被闲置,无法处理其他任务。如果 urls
列表很长或者某些网站响应很慢,程序的效率会极其低下。这就是同步 I/O(输入/输出)操作的瓶颈——I/O 等待消耗了大量时间。
多线程或多进程是传统的解决方案,但它们在高并发场景下也有痛点:
-
线程/进程开销大:创建和切换线程/进程需要消耗可观的 CPU 和内存资源。
-
竞争条件与锁:共享数据时需要复杂的同步机制(如锁),容易出错(死锁、数据竞争)。
-
GIL 限制 (CPython):全局解释器锁 (GIL) 限制了 CPython 中多线程无法真正并行执行 CPU 密集型任务(尽管对 I/O 密集型有改善)。
异步编程:换个思路解决问题
异步编程提供了一种截然不同的范式来解决 I/O 密集型任务的高并发问题。其核心思想是:
当一个任务需要等待 I/O(如网络、磁盘读写)时,不是干等着阻塞整个程序,而是主动让出控制权,让 CPU 去执行其他可以立即运行的任务。当 I/O 操作完成后,再回来继续执行那个被挂起的任务。
这就像是一位高效的餐厅服务员:
-
同步方式:服务员为 A 桌点完菜,一直站在厨房门口等菜做好,期间无视其他顾客的呼唤。
-
异步方式:服务员为 A 桌点完菜,把订单交给厨房,立即转身去服务 B 桌点菜。当厨房通知 A 桌的菜好了,服务员再过去上菜。这样,一个服务员就能同时服务多桌客人。
Python 的答案:asyncio
Python 3.4 引入了 asyncio
标准库,并在后续版本中不断成熟(建议使用 Python 3.7+ 以获得最佳体验)。asyncio
提供了一套完整的框架,用于编写单线程并发代码,通过 协程 (Coroutines)、事件循环 (Event Loop) 和 Future/任务 (Task) 来实现异步 I/O 操作。
核心概念解析
-
协程 (Coroutine):
-
这是异步编程的基本单位。你可以把它看作是一种特殊的函数,它可以在执行过程中暂停 (Suspend) 和
-