Python中为何需在每个async函数前添加await关键字?关于其阻塞特性与异步编程意义的技术问询
嘿,这两个问题绝对是Python异步编程入门的“灵魂拷问”,我来给你唠明白~
一、为啥每个async函数前都得加await?
首先得搞清楚:用async def定义的函数,调用它的时候不会直接执行代码,只会返回一个「协程对象」——你可以把它看成一个“待执行的任务蓝图”。
而await的作用有两个核心点:
- 它会触发协程对象的执行,直到这个协程完成并返回结果;
- 更重要的是,它会告诉事件循环:“我现在要等这个协程的IO操作(比如网络请求、文件读写)完成,你别在这耗着,先去处理其他就绪的任务,等我这边有结果了再回来找我”。
举个直白的例子:
async def get_user_info(user_id): # 模拟从数据库查数据的IO操作 await asyncio.sleep(1) return {"id": user_id, "name": "Alice"} # 直接调用,啥也没执行,只拿到一个协程对象 coro = get_user_info(1) print(coro) # 输出: <coroutine object get_user_info at 0x10abc1234> # 必须用await才会触发执行并拿到结果 result = await get_user_info(1) print(result) # 输出: {"id": 1, "name": "Alice"}
另外要注意:await只能在async函数里用,因为它依赖事件循环的调度机制,普通函数里没这环境。
二、加了await后看起来像阻塞,那异步编程的意义在哪?
这是最容易误解的点!这里的“阻塞”和同步编程里的线程级阻塞完全不是一回事:
- 同步阻塞:比如你用
requests.get()发请求,整个线程会被死死卡住,啥也干不了,直到服务器返回结果; - await的“阻塞”:是协程级别的暂停,线程本身没闲着——事件循环会把当前协程挂起,转而去执行其他已经准备好的协程任务,等当前协程的IO操作完成后,再回来继续执行它后面的代码。
给你看个对比例子就懂了:
import asyncio import time # 模拟IO密集型任务 async def fetch_data(task_name, wait_time): print(f"[{time.strftime('%H:%M:%S')}] 开始执行 {task_name}") await asyncio.sleep(wait_time) # 模拟等待IO结果 print(f"[{time.strftime('%H:%M:%S')}] {task_name} 执行完成") async def main(): # 串行执行:两个任务依次等待,总耗时是两个任务的时间之和 start = time.time() await fetch_data("任务1", 2) await fetch_data("任务2", 2) print(f"串行总耗时: {time.time() - start:.2f}秒\n") # 并行执行:用create_task把协程包装成任务,事件循环同时调度 start = time.time() task1 = asyncio.create_task(fetch_data("任务A", 2)) task2 = asyncio.create_task(fetch_data("任务B", 2)) await task1 await task2 print(f"并行总耗时: {time.time() - start:.2f}秒") asyncio.run(main())
运行后你会看到:
- 串行总耗时大概4秒,两个任务一先一后执行;
- 并行总耗时大概2秒,两个任务几乎同时开始、同时结束。
这里的await task1和await task2只是在等任务完成,但等待期间事件循环已经去跑另一个任务了,根本没浪费时间。
说白了,异步编程的意义就是在IO等待的“空窗期”,把CPU时间利用起来处理其他任务,特别适合网络请求、数据库操作这种IO密集型场景。如果是CPU密集型任务(比如大计算),异步反而不如多线程/多进程,因为Python的GIL锁会限制线程的并行执行。
内容的提问来源于stack exchange,提问作者ibrahim koz
相关产品推荐
相关产品推荐

