首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Asyncio集差

Asyncio集差
EN

Stack Overflow用户
提问于 2022-10-07 14:41:03
回答 1查看 41关注 0票数 1

据我所知,这两个代码块都在做相同的事情。为什么执行时间有差异?

代码语言:javascript
复制
import asyncio
import time
...

# Block 1:
start_time = time.time()
tasks = [
    get_from_knowledge_v2(...),
    get_from_knowledge_v2(...),
    get_from_knowledge_v2(...),
]
data_list = await asyncio.gather(*tasks)
print("TIME TAKEN::", time.time() - start_time)

# Block 2:
start_time = time.time()
data1 = await get_from_knowledge_v2(...)
data2 = await get_from_knowledge_v2(...)
data3 = await get_from_knowledge_v2(...)
print("WITHOUT ASYNCIO GATHER TIME TAKEN::", time.time() - start_time)

结果:

代码语言:javascript
复制
TIME TAKEN:: 0.6016566753387451
WITHOUT ASYNCIO GATHER TIME TAKEN:: 1.7620849609375
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2022-10-07 15:07:36

asyncio.gather函数运行您并发传递给它的awaitables函数。这意味着,如果I/O至少发生在其中一个中,则允许通过事件循环进行有用的上下文切换。这反过来又导致了某种程度的平行。

在本例中,我假设get_from_knowledge_v2以支持异步执行的方式执行某些HTTP请求。

在第二个代码块中,三个调用之间没有并发。相反,您只需依次执行(相对于彼此)。换句话说,当您正在await中的第一个,第二个将不会启动。它们的上下文是阻止的

注意:--这并不意味着在代码块之外不会发生/可能发生并发。如果这个顺序代码块位于async函数(即coroutine)中,则可以与其他协同器并行执行该代码块。只是在这个代码块中,这些get_from_knowledge_v2协同器是按顺序执行的。

您测量的时间很好地证实了这一点,因为您有三个协同器,gather允许它们几乎并行执行,而另一个代码块则按顺序执行它们,从而导致几乎三倍于的执行时间。

PS

也许一个最小的具体例子将有助于说明我的意思:

代码语言:javascript
复制
from asyncio import gather, run, sleep
from time import time


async def sleep_and_print(seconds: float) -> None:
    await sleep(seconds)
    print("slept", seconds, "seconds")


async def concurrent_sleeps() -> None:
    await gather(
        sleep_and_print(3),
        sleep_and_print(2),
        sleep_and_print(1),
    )


async def sequential_sleeps() -> None:
    await sleep_and_print(3)
    await sleep_and_print(2)
    await sleep_and_print(1)


async def something_else() -> None:
    print("Doing something else that takes 4 seconds...")
    await sleep(4)
    print("Done with something else!")


async def main() -> None:
    start = time()
    await concurrent_sleeps()
    print("concurrent_sleeps took", round(time() - start, 1), "seconds\n")

    start = time()
    await sequential_sleeps()
    print("sequential_sleeps took", round(time() - start, 1), "seconds\n")

    start = time()
    await gather(
        sequential_sleeps(),
        something_else(),
    )
    print("sequential_sleeps & something_else together took", round(time() - start, 1), "seconds")


if __name__ == '__main__':
    run(main())

运行该脚本将提供以下输出:

代码语言:javascript
复制
slept 1 seconds
slept 2 seconds
slept 3 seconds
concurrent_sleeps took 3.0 seconds

slept 3 seconds
slept 2 seconds
slept 1 seconds
sequential_sleeps took 6.0 seconds

Doing something else that takes 4 seconds...
slept 3 seconds
Done with something else!
slept 2 seconds
slept 1 seconds
sequential_sleeps & something_else together took 6.0 seconds

这说明睡眠几乎是在concurrent_sleeps内部并行进行的,1秒的睡眠首先完成,然后是2秒的睡眠,然后是3秒的睡眠。

它显示睡眠是在sequential_sleeps内按调用顺序依次进行的,这意味着它首先睡了3秒,然后睡了2秒,然后是1秒。

最后,与something_else并发执行something_else显示,它们几乎是并行执行的,3秒睡眠首先完成(3秒后),1秒后something_else完成,然后2秒睡眠结束,然后1秒后1秒睡眠。加在一起,他们仍然花了大约6秒。

最后一部分是我的意思,当我说你仍然在执行另一个协同线的同时,与顺序的代码块。就其本身而言,代码块将始终保持顺序。

我希望现在的情况更清楚。

PPS

为了在混合中抛出另一个选项,您也可以使用Task来实现并发。调用asyncio.create_task将立即调度协同线,以便在事件循环上执行。它创建的任务应该在某个时候等待,但是底层协同几乎会在调用create_task之后立即开始运行。您可以将其添加到上面的示例脚本中:

代码语言:javascript
复制
from asyncio import create_task
...
async def task_sleeps() -> None:
    t3 = create_task(sleep_and_print(3))
    t2 = create_task(sleep_and_print(2))
    t1 = create_task(sleep_and_print(1))
    await t3
    await t2
    await t1

async def main() -> None:
    ...
    start = time()
    await task_sleeps()
    print("task_sleeps took", round(time() - start, 1), "seconds\n")

你会再次看到以下情况:

代码语言:javascript
复制
...
slept 1 seconds
slept 2 seconds
slept 3 seconds
task_sleeps took 3.0 seconds

任务是一个很好的选择,可以在一定程度上将某些协同机制的执行与其周围的上下文分离开来,但您需要以某种方式跟踪它们。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/73988859

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档