據我了解,兩個代碼塊都在做同樣的事情。為什么執行時間有差異?
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)
結果:
TIME TAKEN:: 0.6016566753387451
WITHOUT ASYNCIO GATHER TIME TAKEN:: 1.7620849609375
uj5u.com熱心網友回復:
該asyncio.gather函式同時運行您傳遞給它的等待物件。這意味著,如果 I/O 至少發生在其中一個中,則允許事件回圈進行有用的背景關系切換。這反過來又導致了一定程度的并行性。
在這種情況下,我假設它get_from_knowledge_v2以支持異步執行的方式執行一些 HTTP 請求。
在第二個代碼塊中,三個呼叫之間沒有并發。get_from_knowledge_v2相反,您只需按順序執行它們(相對于彼此)。換句話說,當您正在await執行其中的第一個時,第二個將不會啟動。他們的背景關系被封鎖了。
注意:這并不意味著在該代碼塊之外沒有發生/可能的并發。如果這個順序代碼塊在一個async函式(即協程)內,您可以與其他協程同時執行該代碼塊。只是在那個代碼塊內,那些get_from_knowledge_v2協程是按順序執行的。
您測量的時間很好地證實了這一點,因為您有三個協程并gather允許它們幾乎并行執行,而另一個代碼塊按順序執行它們,從而導致執行時間幾乎延長了三倍。
附言
也許一個最小的具體例子將有助于說明我的意思:
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())
運行該腳本會給出以下輸出:
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 秒。
最后,sequential_sleeps同時執行something_else表明它們幾乎是并行執行的,3 秒睡眠首先完成(3 秒后),然后一秒后something_else完成,再過一秒 2 秒睡眠,然后又一個第二個 1 秒睡眠。他們一起仍然需要大約 6 秒。
最后一部分就是我的意思,當我說你仍然與順序代碼塊同時執行另一個協程時。就其本身而言,代碼塊仍將始終保持順序。
我希望這現在更清楚了。
聚苯乙烯
只是為了混合另一個選項,您還可以通過使用Tasks 來實作并發。呼叫asyncio.create_task將立即安排協程在事件回圈上執行。它創建的任務應該在某個時候等待,但底層協程將在呼叫后幾乎立即開始運行create_task。您可以將其添加到上面的示例腳本中:
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")
您將再次看到以下內容:
...
slept 1 seconds
slept 2 seconds
slept 3 seconds
task_sleeps took 3.0 seconds
任務是一個很好的選擇,可以在一定程度上將某些協程的執行與其周圍的背景關系分離,但您需要以某種方式跟蹤它們。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/517881.html
