Python asyncio协程何时真正让出执行权?
问题场景
我在研究Python的asyncio模块时,了解到需要通过await协程获取结果,且await时当前协程会把执行权交给事件循环,让其他就绪协程运行。结合以下代码示例,有两个疑问:
async def parse_data(idx,pool): await compute_and_fetch_data() # Do some work async def compute_and_fetch_data(idx, pool): # 这里是CPU密集型计算任务,比如复杂运算并生成查询语句 # 示例中硬编码查询语句 query = "SELECT * FROM customers" async with pool.acquire() as conn: async with conn.cursor() as cur: await cur.execute(query) result = await cur.fetchall() logger.info(f"Fetched result for query {idx+1} successfully.") return result async def main(): pool = await aiomysql.create_pool(host='localhost', user='user', password='password', db='laundry') asyncio.gather(parse_data(1, pool)) asyncio.run(main())
具体疑问
- 在
parse_data协程中执行到await compute_and_fetch_data()时,是立即让出执行权给事件循环,还是先执行compute_and_fetch_data里的CPU密集型计算,直到该协程遇到数据库查询的await(耗时IO操作)时才让出执行权? - 如果是后者,那“当前协程在遇到
await关键字时让出执行权”的说法是否仍然成立?
解答
执行权不会立即让出:当
parse_data执行到await compute_and_fetch_data()时,它会暂停自身执行,转而启动compute_and_fetch_data协程的执行。此时事件循环并不会立刻接管,而是先跑完compute_and_fetch_data中所有非await的同步代码——也就是你写的CPU密集型计算部分,直到compute_and_fetch_data内部遇到第一个await(比如async with pool.acquire()里隐含的await操作,或者后面的await cur.execute(query)),这时候整个当前执行链才会把执行权交还给事件循环,让其他就绪协程运行。原说法仍然成立,但要准确理解:“当前协程遇到
await时让出执行权”的表述没错,但这里的“当前协程”指的是正在运行的那个协程。当parse_data发起await时,它只是暂停自己,把执行权转移给被await的compute_and_fetch_data;而compute_and_fetch_data作为此时的“当前协程”,在自己内部遇到await时,才会触发执行权让出给事件循环。整个过程中,每一次await都是当前运行的协程在遇到它时暂停并交出执行权,只是外层协程的await是把执行权转给了子协程,而非直接给事件循环而已。
内容的提问来源于stack exchange,提问作者Rahul Gusai

