簡體   English   中英

在asyncio中使用run_in_executor時,事件循環是否在主線程中執行?

[英]When using run_in_executor in asyncio, is the event loop executed in the main thread?

我正在嘗試使用 asyncio 功能進行多線程執行。
我的理解是循環管理線程並在線程上執行函數。
如果是這種情況,那么當主線程仍在處理時,應該不可能啟動一個新的 function。
但是,當我運行以下代碼時,它會在我睡着的時候開始執行新的 function。 你能告訴我原因嗎?

參考: https://stackoverflow.com/a/60747799

import asyncio
from concurrent.futures.thread import ThreadPoolExecutor
from time import sleep
import logging

logging.basicConfig(
    level=logging.DEBUG, format="%(asctime)s %(thread)s %(funcName)s %(message)s"
)

def long_task(t):
    """Simulate long IO bound task."""
    logging.info("2. t: %s", t)
    sleep(t)
    logging.info("5. t: %s", t)
    return t ** 2

async def main():
    loop = asyncio.get_running_loop()
    executor = ThreadPoolExecutor(max_workers=2)
    inputs = range(1, 5)
    logging.info("1.")
    futures = [loop.run_in_executor(executor, long_task, i) for i in inputs]
    logging.info("3.")
    sleep(3)
    logging.info("4.")
    results = await asyncio.gather(*futures)
    logging.info("6.")

if __name__ == "__main__":
    asyncio.run(main())

預計 output

2022-02-08 22:59:08,896 139673219430208 __init__ Using selector: EpollSelector
2022-02-08 22:59:08,896 139673219430208 main 1.
2022-02-08 22:59:08,897 139673194632960 long_task 2. t: 1
2022-02-08 22:59:08,897 139673186240256 long_task 2. t: 2
2022-02-08 22:59:08,897 139673219430208 main 3.
2022-02-08 22:59:09,898 139673194632960 long_task 5. t: 1
2022-02-08 22:59:10,898 139673186240256 long_task 5. t: 2
2022-02-08 22:59:13,400 139673219430208 main 4.
2022-02-08 22:59:09,898 139673194632960 long_task 2. t: 3
2022-02-08 22:59:10,899 139673186240256 long_task 2. t: 4
2022-02-08 22:59:12,902 139673194632960 long_task 5. t: 3
2022-02-08 22:59:14,903 139673186240256 long_task 5. t: 4
2022-02-08 22:59:14,903 139673219430208 main 6.

實際 output

2022-02-08 22:59:08,896 139673219430208 __init__ Using selector: EpollSelector
2022-02-08 22:59:08,896 139673219430208 main 1.
2022-02-08 22:59:08,897 139673194632960 long_task 2. t: 1
2022-02-08 22:59:08,897 139673186240256 long_task 2. t: 2
2022-02-08 22:59:08,897 139673219430208 main 3.
2022-02-08 22:59:09,898 139673194632960 long_task 5. t: 1
2022-02-08 22:59:09,898 139673194632960 long_task 2. t: 3
2022-02-08 22:59:10,898 139673186240256 long_task 5. t: 2
2022-02-08 22:59:10,899 139673186240256 long_task 2. t: 4
2022-02-08 22:59:12,902 139673194632960 long_task 5. t: 3
2022-02-08 22:59:13,400 139673219430208 main 4.
2022-02-08 22:59:14,903 139673186240256 long_task 5. t: 4
2022-02-08 22:59:14,903 139673219430208 main 6.

在 3 和 4 之間,sleep(3) 正在主線程中執行。 我明白Threadpool中早先運行的longtask(1)和longtask(2)的結束是在這段時間打印出來的,但是為什么下一個任務在這段時間運行呢? 如果 event_loop 在主線程中,那么 sleep(3) 應該不允許執行新的 function。

在asyncio中使用run_in_executor時,事件循環是否在主線程中執行?

是的,它是 - 但run_in_executor將可調用對象提交給執行程序,允許它們在沒有事件循環幫助的情況下運行。

在 3 和 4 之間,sleep(3) 正在主線程中執行。 我明白Threadpool中早先運行的longtask(1)和longtask(2)的結束是在這段時間打印出來的,但是為什么下一個任務在這段時間運行呢? 如果 event_loop 在主線程中,那么 sleep(3) 應該不允許執行新的 function。

ThreadPoolExecutor(max_workers=2)創建一個最多可擴展到兩個工作線程的線程池。 run_in_executorExecutor.submit的包裝器,可確保將最終結果傳播到 asyncio。 它的實現可能看起來像這樣( 實際代碼有點復雜,因為它處理取消和其他問題,但這是要點):

class EventLoop:
    # ...
    def run_in_executor(self, executor, f, *args):
        async_future = self.create_future()
        handle = executor.submit(f, *args)
        def when_done(_):
            self.call_soon_threadsafe(async_future.set_result, handle.result())
        handle.add_done_callback(when_done)
        return async_future

submit調用將可調用對象及其 arguments 推入多線程隊列。 池中的工作人員在消耗該隊列的無限循環中運行,僅在執行者被告知關閉時退出。

如果您提交的任務多於池中工作人員的數量,則額外的任務仍將放置在隊列中,等待輪到它們進行處理。 (隊列是一個無界通道,因此Executor.submit()永遠不會阻塞。)一旦工作人員完成任務,它將請求隊列中的下一個任務,這就是執行額外任務的原因。 主線程此時卡在time.sleep()中並不重要 - 函數在此之前已提交給執行程序,並且位於隊列中,因此工作人員可以很好地訪問它們。


最后,在普通的異步代碼中,異步 function 絕不能調用 time.sleep time.sleep() ,它必須await asyncio.sleep() (我知道你是故意阻止運行事件循環的線程,但初學者往往不知道這一點,所以需要指出。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM