为何使用asyncio异步for循环的代码与同步代码耗时相同?
关于Asyncio异步生成器示例代码的疑惑
我正在学习O'Reilly出版的《Using Asyncio in Python》一书,书中给出了一个使用async for循环的异步生成器示例代码,运行后耗时约9秒。我认为这段代码并未真正异步执行,于是编写了同步版本的代码进行对比,结果两者耗时几乎相同。我疑惑这段示例代码是否真的是异步代码,无法理解为何书中将其称为异步代码。
书中的异步示例代码(Example 3-26)
# Example 3-26. Easier with an async generator import asyncio import time # 原代码遗漏time模块导入,此处补充 # Mock Redis interface class Redis: async def get(self, key): await asyncio.sleep(1) return 'value' # Mock create_redis # Real one: aioredis.create_redis async def create_redis(socket): await asyncio.sleep(1) return Redis() async def do_something_with(value): await asyncio.sleep(1) # Our function is now declared with async def , making it a coroutine # function, and since this function also contains the yield keyword, we refer # to it as an asynchronous generator function. async def one_at_a_time(redis, keys): for k in keys: # We don’t have to do the convoluted things necessary in the previous # example with self.ikeys: here, we just loop over the keys directly # and obtain the value... value = await redis.get(k) # ...and then yield it to the caller, just like a normal generator. yield value # The main() function is identical to the version in Example 3-25. async def main(): redis = await create_redis(('localhost', 6379)) keys = ['Americas', 'Africa', 'Europe', 'Asia'] async for value in one_at_a_time(redis, keys): await do_something_with(value) start = time.time() asyncio.run(main()) end = time.time() print(end - start) # print result is 9.012349128723145
我编写的同步对比代码
import time class Redis: def get(self, key): time.sleep(1) return 'value' def create_redis(socket): time.sleep(1) return Redis() def do_something_with(value): time.sleep(1) def one_at_a_time(redis, keys): for k in keys: value = redis.get(k) yield value # The main() function is identical to the version in Example 3-25. def main(): redis = create_redis(('localhost', 6379)) keys = ['Americas', 'Africa', 'Europe', 'Asia'] tasks = [] for value in one_at_a_time(redis, keys): do_something_with(value) start = time.time() main() end = time.time() print(end-start) # print result 9.025717973709106
问题解答
这段代码确实是异步代码,但逻辑是串行执行的
代码使用了async def、await、async for等异步语法,符合异步代码的定义,但它的执行流程是完全串行的:- 先等待
create_redis完成(1秒) - 然后逐个处理每个key:等待
redis.get完成(1秒)→ 等待do_something_with完成(1秒),4个key就是4×2=8秒 - 总耗时1+8=9秒,和同步版本的耗时完全对应
- 先等待
书中称它为异步代码的原因
这个示例的核心是展示异步生成器的语法和用法:用async def + yield定义异步生成器,用async for遍历异步生成器产出的值。它的目的不是演示并发执行的效果,而是展示如何用更简洁的方式逐步获取异步操作的结果,替代之前需要手动实现异步迭代器的复杂写法。如果要实现并发,需要调整代码逻辑
要让这段代码真正体现异步并发的优势,需要把多个redis.get和do_something_with任务同时提交给事件循环,比如使用asyncio.gather:async def main(): redis = await create_redis(('localhost', 6379)) keys = ['Americas', 'Africa', 'Europe', 'Asia'] # 同时创建所有任务并等待完成 tasks = [do_something_with(await redis.get(k)) for k in keys] await asyncio.gather(*tasks)这样总耗时会变成1(create_redis)+1(get的并发)+1(do的并发)=3秒,就能体现异步并发的效率提升。
内容的提问来源于stack exchange,提问作者jihyeon
相关产品推荐
相关产品推荐

