十年前我第一次接触异步编程时,面对回调地狱(callback hell)的代码简直怀疑人生。直到Python 3.4引入asyncio,才让异步代码有了同步写法般的优雅。异步编程的核心在于用单线程处理高并发,这与传统多线程有本质区别。
想象你去餐厅点餐:同步模式就像只有一个服务员,必须等前一个顾客完成点餐才能服务下一个;多线程像是给每个顾客分配专属服务员;而异步模式则是那个能同时照顾十桌客人的"超级服务员"——当A顾客看菜单时,他就去服务B顾客,等A决定好了再回来继续服务。
asyncio的实现依赖于三个关键技术:
关键认知:async/await不是语法糖,而是改变程序执行流程的控制器。当函数执行到await时,会主动让出控制权给事件循环。
事件循环是asyncio的心脏,它的工作流程可以拆解为:
用代码表示核心逻辑:
python复制while tasks:
# 处理已完成IO
ready = selector.select(timeout)
for fd, events in ready:
callback = fd_to_callback[fd]
tasks.append(callback)
# 执行就绪任务
current = tasks.popleft()
try:
next_step = current.send(result)
register_io_callback(next_step)
except StopIteration:
pass
SelectorEventLoop (默认)
ProactorEventLoop (Windows专用)
UvloopEventLoop (第三方)
实测数据:在10K并发连接测试中,uvloop的QPS比默认循环高出300%
Python协程经历了三次进化:
关键区别在于:
python复制# 三种协程写法对比
def old_coroutine():
yield from asyncio.sleep(1)
@asyncio.coroutine
def decorated_coroutine():
yield from asyncio.sleep(1)
async def native_coroutine():
await asyncio.sleep(1)
当调用async函数时:
协程暂停时保存的关键状态:
常见并发控制方式对比:
| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| gather() | 简单易用 | 全部成功或失败 | 批量任务 |
| wait() | 灵活控制 | 需手动处理结果 | 需要超时控制 |
| as_completed() | 实时获取结果 | 无序返回 | 流式处理 |
| Semaphore | 精确控制 | 需手动管理 | 限流场景 |
推荐使用信号量的正确姿势:
python复制sem = asyncio.Semaphore(10)
async def limited_task(url):
async with sem:
return await fetch(url)
IO密集型场景推荐架构:
code复制主线程: asyncio事件循环
└── 工作线程池: 执行CPU密集型任务
└── 通过run_in_executor与主循环交互
关键代码示例:
python复制def cpu_bound(x):
return sum(i*i for i in range(x))
async def main():
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(
None, cpu_bound, 1000000)
python复制asyncio.run(coro(), debug=True)
会检测未等待的协程
python复制from pyinstrument import Profiler
async def main():
profiler = Profiler()
profiler.start()
# 你的代码
profiler.stop()
print(profiler.output_text())
问题1:协程忘记await
python复制async def demo():
asyncio.sleep(1) # 错误!缺少await
问题2:阻塞事件循环
python复制async def bad():
time.sleep(1) # 同步阻塞
问题3:任务泄漏
python复制async def leak():
asyncio.create_task(background_job()) # 未保存引用
解决方案:
典型异步应用分层:
code复制表示层 (API路由)
↓
业务逻辑层 (纯协程)
↓
数据访问层 (异步DB驱动)
↓
外部服务 (aiohttp等)
数据库连接池最佳实践:
python复制async def get_pool():
return await asyncpg.create_pool(
min_size=5,
max_size=20,
command_timeout=60,
host='localhost')
HTTP客户端优化配置:
python复制conn = aiohttp.TCPConnector(
limit=100,
limit_per_host=20,
enable_cleanup_closed=True)
Python 3.11引入的TaskGroup彻底改变了任务管理方式:
python复制async with asyncio.TaskGroup() as tg:
tg.create_task(task1())
tg.create_task(task2())
# 自动等待所有任务完成
相比旧模式的优势:
在最近的一个爬虫项目中,使用TaskGroup后代码行数减少了40%,错误处理逻辑简化了70%。一个典型的网络服务启动代码现在可以如此简洁:
python复制async def serve():
async with (
asyncio.TaskGroup() as tg,
DatabasePool() as db,
RedisConnection() as redis
):
tg.create_task(web_server())
tg.create_task(background_job())
tg.create_task(health_check())