python asyncio.gather vs asyncio.as_completed 当 IO 任务后跟 CPU 绑定任务

时间:2021-05-25 23:15:54

标签: python python-asyncio

我有一个程序工作流程如下:1. IO-bound(网页获取)-> 2. cpu-bound(处理信息)-> 3. IO-bound(将结果写入数据库)。

我目前正在使用 aiohttp 来获取网页。我目前正在使用 asyncio.as_completed 来收集步骤 1 的任务,并在完成时将它们传递给步骤 2。我担心的是,这可能会通过消耗 cpu 资源并阻塞步骤 2 中的程序流来干扰步骤 1 任务的完成。

我尝试使用 ProcessPoolExecutor 将第 2 步任务分派给其他进程,但第 2 步任务使用不可pickleable 数据结构和函数。我已经尝试过 ThreadPoolExecutor,虽然它有效(例如它没有崩溃),但我的理解是,对 CPU 密集型任务这样做会适得其反。

因为工作流有一个中间 cpu 绑定任务,在转到步骤 2 之前使用 asyncio.gather(而不是 asyncio.as_completed)完成所有步骤 1 过程会更有效吗?

示例 asyncio.as_completed 代码:

async with ClientSession() as session:
    tasks = {self.fetch(session, url) for url in self.urls}
    for task in asyncio.as_completed(tasks):
        raw_data = await asyncio.shield(task)
        data = self.extract_data(*raw_data)
        await self.store_data(data)

示例 asyncio.gather 代码:

async with ClientSession() as session:
    tasks = {self.fetch(session, url) for url in self.urls}
    results = await asyncio.gather(*tasks)
for result in results:
    data = self.extract_data(*result)
    await self.store_data(data)

有限样本的初步测试显示 as_completed 比gather 稍微高效一点:~2.98s (as_completed) vs ~3.15s (gather)。但是是否存在一个比另一种更偏向于一种解决方案的异步概念问题?

1 个答案:

答案 0 :(得分:0)

“我已经尝试过 ThreadPoolExecutor,[...] 我的理解是,对 CPU 密集型任务这样做会适得其反。” - 从某种意义上说,这是一种反生产力,你不会有两个这样的问题并行运行 Python 代码,使用多个 CPU 内核 - 但否则,它将工作以释放你的异步循环以继续工作,如果一次只为一项任务咀嚼代码。

如果你不能把东西放到子进程中,那么在 ThreadPoolExecutor 中运行 CPU 绑定任务就足够了。

否则,只需在 cpu 代码中添加一些 await asyncio.sleep(0)(在循环内)并将它们作为协程正常运行:这足以让 cpu 绑定任务不锁定异步循环。

相关问题