为了账号安全,请及时绑定邮箱和手机立即绑定

python asyncio as_completed的疑惑

python asyncio as_completed的疑惑

慕无忌1623718 2024-01-27 15:28:39
在阅读了相当多关于 asyncio 的内容之后(我对它完全是菜鸟),我已经成功编写了一些简单的程序来完成我想要它们做的事情。然而,我对 as_completed 方法有一些疑问:它的内部工作原理以及它如何影响我的 CPU 使用率。因此,让有以下片段:#as_completed_example.pyimport asyncioimport tqdmimport datetimeimport sysimport signalimport random#--------------------------------------------------------------------------------------async def heavy_load(i):    #tqdm.tqdm.write('#DEBUG    '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+' '+str(i))    await asyncio.sleep(random.random())    return None#--------------------------------------------------------------------------------------async def main():    length  =   int(sys.argv[1])    inputs  =   list(range(length))    pbar    =   tqdm.tqdm(total=len(inputs),position=0,leave=True,bar_format='#PROGRESS {desc}: {percentage:.3f}%|{bar}| {n_fmt}/{total_fmt} [{elapsed}<{remaining}')    tasks   =   [heavy_load(i) for i in inputs]    for future in asyncio.as_completed(tasks):        _ = await future        pbar.update(1)        pbar.refresh()            #---------------------------------------------------------------------------    def sigint_handler(signum,frame):    tqdm.tqdm.write('#INFO '+datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')+' user aborted execution!')    sys.exit(0)#---------------------------------------------------------------------------    if(__name__=='__main__'):    signal.signal(signal.SIGINT,sigint_handler)    asyncio.run(main())     如果我将其称为 python3 as_completed_example.py 1000,它会完美地工作。然而,如果我将其称为as_completed_example.py 1000000 (large number),我会观察到我的进度条在相当长的一段时间内卡在 0% :-虽然我的进度条为 0%,--我的 CPU 发生了什么?因为需要一个核心才能达到 100% 使用率-为什么我future在相当长一段时间后没有从 as_completed得到任何信息?
查看完整描述

1 回答

?
猛跑小猪

TA贡献1858条经验 获得超8个赞

这里有几个问题,如果可能的话应该避免。但对于你的前两个问题有一个简单的答案。为什么它固定单个CPU,为什么在打印进度条之前有延迟?有很多单线程工作要做。

asyncio在单个线程中运行所有内容,除非您明确这样做。您正在构建的任务需要在 内部进行大量设置asyncio,尤其是对as_completed. 但它必须:

  • 创造set你的未来。不太贵,但不是免费的。

  • 设置生产者和消费者队列来控制尚未运行的任务和已完成的任务。对于您正在使用的如此大量的任务,这可能会导致多次、大量的分配,这可能是一个真正的杀手。

  • 安排回调在 future 完成时运行。这主要是将它们从队列移动到另一个队列,并将它们从setfuture 中删除,这些都不是免费的。

  • 成就每一个未来

事实上,这里有很多设置,并且这需要相当多的时间,通过改变输入的大小可以很容易地看到这一点。在我的笔记本电脑上,运行任何期货之前的时间确实从 size 开始急剧下降100000。此外,它会非线性下降,这表明这个大小对于我的机器上的内存层次结构特别不利(例如,在这个大小之后,会有更多的缓存未命中)。

asyncio我还发现事件循环的解析可能在这里发挥了作用。期货的消耗必须经过一定的时间。您正在创建许多 future,其中许多几乎是在事件循环的单个滴答内同时完成的(正如 @user4815162342 在评论中正确指出的那样)。事件循环的每个周期都有大量工作,并且必须全部在单个线程上完成。

考虑到整个事情需要超过 1 秒才能完成这一事实,这一点非常清楚。最大睡眠间隔为 1 秒,因为random.random为您提供了 上的值[0, 1.0),但整个应用程序需要更长的时间。因此,这里正在进行的工作不仅仅是“一秒钟的价值”,而且全部都在一个线程中进行。


查看完整回答
反对 回复 2024-01-27
  • 1 回答
  • 0 关注
  • 142 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信