为何等待协程仍被视为非阻塞?异步机制核心疑问解析
问题1:既然需要等待,为何仍被视为异步操作?
同步代码中,每发起一个网络请求,整个线程会被完全阻塞——啥也干不了,必须等这个请求的响应返回、解析完成,才能执行下一个请求。总耗时是所有请求耗时的累加。
而异步代码里,await的等待是非阻塞式等待:当你await一个耗时操作(比如网络请求)时,当前协程会暂停执行,但事件循环不会闲置,它会立刻去调度其他已经准备好的协程(比如发起下一个请求)。所有请求是并发发起的,总耗时接近单个请求的最长耗时,而非所有请求耗时之和,这就是异步性能提升的核心原因。
问题2:“将控制权交还给事件循环”具体指什么?这是不是意味着await resp.json()之后的print语句可以提前执行?
“交还控制权给事件循环”的意思是:当前协程因await某个未完成的操作(比如等待网络响应)暂停时,CPU的控制权会交还给事件循环,由它去执行其他就绪的协程(比如处理下一个请求的发送)。
但**await之后的代码绝对不会提前执行**。await的语义就是“必须等这个操作完成后,再继续执行后续代码”。对单个协程来说,pokemon = await resp.json()之后的print(pokemon['name']),必须等JSON解析完成才会运行。只是在等待的这段空窗期里,事件循环可以去处理其他协程的任务,实现了并发效率的提升。
代码对比解析
异步代码(150次GET请求)
import aiohttp import asyncio import time start_time = time.time() async def main(): async with aiohttp.ClientSession() as session: for number in range(1, 151): pokemon_url = f'https://pokeapi.co/api/v2/pokemon/{number}' async with session.get(pokemon_url) as resp: pokemon = await resp.json() print(pokemon['name']) asyncio.run(main()) print("--- %s seconds ---" % (time.time() - start_time))
循环中每个session.get异步发起请求,遇到await resp.json()时当前协程暂停,事件循环立刻切换到下一个请求的发起。所有请求几乎同时发出,总耗时大幅缩短。
同步代码(150次GET请求)
import requests import time start_time = time.time() for number in range(1, 151): url = f'https://pokeapi.co/api/v2/pokemon/{number}' resp = requests.get(url) pokemon = resp.json() print(pokemon['name']) print("--- %s seconds ---" % (time.time() - start_time))
requests.get是阻塞式调用,发起请求后线程完全停滞,必须等响应返回、解析完成,才能执行下一个请求。150个请求串行执行,总耗时是所有请求耗时的累加,效率远低于异步版本。
内容的提问来源于stack exchange,提问作者user1224478

