簡體   English   中英

如何將python asyncio與線程結合起來?

[英]How to combine python asyncio with threads?

我已經成功地使用 Python asyncio 和 aiohttp 構建了一個RESTful 微服務,它偵聽 POST 事件以從各種饋送器收集實時事件。

然后它構建一個內存結構以在嵌套的 defaultdict/deque 結構中緩存最后 24 小時的事件。

現在我想定期檢查該結構到磁盤,最好使用泡菜。

由於內存結構可能大於 100MB,我想避免在檢查點結構所需的時間內阻止傳入事件處理。

我寧願創建結構的快照副本(例如深拷貝),然后花時間將其寫入磁盤並在預設的時間間隔內重復。

我一直在尋找有關如何組合線程的示例(線程甚至是最好的解決方案?)和 asyncio 為此目的,但找不到對我有幫助的東西。

任何入門指南都非常感謝!

使用BaseEventLoop.run_in_executor將方法委托給線程或子進程非常簡單:

import asyncio
import time
from concurrent.futures import ProcessPoolExecutor

def cpu_bound_operation(x):
    time.sleep(x) # This is some operation that is CPU-bound

@asyncio.coroutine
def main():
    # Run cpu_bound_operation in the ProcessPoolExecutor
    # This will make your coroutine block, but won't block
    # the event loop; other coroutines can run in meantime.
    yield from loop.run_in_executor(p, cpu_bound_operation, 5)


loop = asyncio.get_event_loop()
p = ProcessPoolExecutor(2) # Create a ProcessPool with 2 processes
loop.run_until_complete(main())

至於是使用ProcessPoolExecutor還是ThreadPoolExecutor ,這有點難說; 酸洗一個大對象肯定會消耗一些 CPU 周期,這最初會讓你認為ProcessPoolExecutor是要走的路。 但是,將 100MB 對象傳遞給池中的Process需要在主進程中對實例進行酸洗,通過 IPC 將字節發送到子進程,在子進程中對其進行解壓,然后再次進行酸洗,以便您可以將其寫入磁盤. 鑒於此,我的猜測是酸洗/取消酸洗開銷將足夠大,您最好使用ThreadPoolExecutor ,即使您會因為 GIL 而受到性能影響。

也就是說,測試兩種方式並確定答案非常簡單,因此您不妨這樣做。

我也使用了run_in_executor ,但我發現這個函數在大多數情況下有點粗糙,因為它需要partial()關鍵字 args 並且除了單個執行器和默認事件循環之外,我從不使用任何其他方法調用它。 所以我用合理的默認值和自動關鍵字參數處理圍繞它做了一個方便的包裝器。

from time import sleep
import asyncio as aio
loop = aio.get_event_loop()

class Executor:
    """In most cases, you can just use the 'execute' instance as a
    function, i.e. y = await execute(f, a, b, k=c) => run f(a, b, k=c) in
    the executor, assign result to y. The defaults can be changed, though,
    with your own instantiation of Executor, i.e. execute =
    Executor(nthreads=4)"""
    def __init__(self, loop=loop, nthreads=1):
        from concurrent.futures import ThreadPoolExecutor
        self._ex = ThreadPoolExecutor(nthreads)
        self._loop = loop
    def __call__(self, f, *args, **kw):
        from functools import partial
        return self._loop.run_in_executor(self._ex, partial(f, *args, **kw))
execute = Executor()

...

def cpu_bound_operation(t, alpha=30):
    sleep(t)
    return 20*alpha

async def main():
    y = await execute(cpu_bound_operation, 5, alpha=-2)

loop.run_until_complete(main())

另一種選擇是使用loop.call_soon_threadsafeasyncio.Queue作為通信的中間通道。

Python 3 的當前文檔還有一節關於使用 asyncio進行開發 - 並發和多線程

import asyncio

# This method represents your blocking code
def blocking(loop, queue):
    import time
    while True:
        loop.call_soon_threadsafe(queue.put_nowait, 'Blocking A')
        time.sleep(2)
        loop.call_soon_threadsafe(queue.put_nowait, 'Blocking B')
        time.sleep(2)

# This method represents your async code
async def nonblocking(queue):
    await asyncio.sleep(1)
    while True:
        queue.put_nowait('Non-blocking A')
        await asyncio.sleep(2)
        queue.put_nowait('Non-blocking B')
        await asyncio.sleep(2)

# The main sets up the queue as the communication channel and synchronizes them
async def main():
    queue = asyncio.Queue()
    loop = asyncio.get_running_loop()

    blocking_fut = loop.run_in_executor(None, blocking, loop, queue)
    nonblocking_task = loop.create_task(nonblocking(queue))

    running = True  # use whatever exit condition
    while running:
        # Get messages from both blocking and non-blocking in parallel
        message = await queue.get()
        # You could send any messages, and do anything you want with them
        print(message)

asyncio.run(main())

如何將 asyncio 任務發送到在其他線程中循環運行也可能對您有所幫助。

暫無
暫無

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

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