簡體   English   中英

使用 asyncio 進行多進程隊列同步

[英]Multiprocess Queue synchronization with asyncio

我想從asyncio 3.7 的兄弟進程中運行的異步循環中收集數據

理想情況下,我會使用multiprocess.JoinableQueue ,中繼其join()調用以進行同步。

但是,它的同步原語完全阻止了事件循環(有關示例,請參見下面我的部分回答)。

說明原型:

class MP_GatherDict(dict):
    '''A per-process dictionary which can be gathered from a single one'''
    def __init__(self):
        self.q = multiprocess.JoinableQueue()
        super().__init__()

    async def worker_process_server(self):
        while True:
            (await?) self.q.put(dict(self)) # Put a shallow copy
            (await?) self.q.join() # Wait for it to be gathered

    async def gather(self):
        all_dicts = []
        while not self.q.empty():
            all_dicts.append(await self.q.get())
            self.q.task_done()
        return all_dicts

請注意, put->get->join->put流程可能無法按預期工作,但這個問題實際上是關於在asyncio事件循環中使用multiprocess進程原語......

那么問題就是如何最好地await來自 asyncio 事件循環的multiprocess進程原語?

此測試顯示multiprocess.Queue.get()阻止整個事件循環:

mp_q = mp.JoinableQueue()
async def mp_queue_wait():
    try:
        print('Queue:',mp_q.get(timeout=2))
    except Exception as ex:
        print('Queue:',repr(ex))

async def main_loop_task():
    task = asyncio.get_running_loop().create_task(mp_queue_wait())
    for i in range(3):
        print(i, os.times())
        await asyncio.sleep(1)
    await task
    print(repr(task))

asyncio.run(main_loop_task())

誰的輸出是:

0 posix.times_result(user=0.41, system=0.04, children_user=0.0, children_system=0.0, elapsed=17208620.18)
Queue: Empty()
1 posix.times_result(user=0.41, system=0.04, children_user=0.0, children_system=0.0, elapsed=17208622.18)
2 posix.times_result(user=0.41, system=0.04, children_user=0.0, children_system=0.0, elapsed=17208623.18)
<Task finished coro=<mp_queue_wait() done,...> result=None>

所以我看asyncio.loop.run_in_executor()作為下一個可能的答案,但是為此產生一個執行器/線程似乎有點過分......

這是使用默認執行程序的相同測試:

async def mp_queue_wait():
    try:
        result = await asyncio.get_running_loop().run_in_executor(None,mp_q.get,True,2)
    except Exception as ex:
        result = ex
    print('Queue:',repr(result))
    return result 

並且(期望的)結果:

0 posix.times_result(user=0.36, system=0.02, children_user=0.0, children_system=0.0, elapsed=17210674.65)
1 posix.times_result(user=0.37, system=0.02, children_user=0.0, children_system=0.0, elapsed=17210675.65)
Queue: Empty()
2 posix.times_result(user=0.37, system=0.02, children_user=0.0, children_system=0.0, elapsed=17210676.66)
<Task finished coro=<mp_queue_wait() done, defined at /home/apozuelo/Documents/5G_SBA/Tera5G/services/db.py:211> result=Empty()>

這有點晚了,但是。

您需要圍繞mp.JoinableQueue()創建一個異步包裝器,因為get()put()都會阻塞整個過程 (GIL)。

有兩種方法:

  1. 使用線程
  2. 使用asyncio.sleep()get_nowait()put_nowait()方法。

我選擇了選項 2,因為它很簡單。

from queue import Queue, Full, Empty
from typing import Any, Generic, TypeVar
from asyncio import sleep

T= TypeVar('T')

class AsyncQueue(Generic[T]):
    """Async wrapper for queue.Queue"""

    SLEEP: float = 0.01

    def __init__(self, queue: Queue[T]):
        self._Q : Queue[T] = queue


    async def get(self) -> T:
        while True:
            try:
                return self._Q.get_nowait()
            except Empty:
                await sleep(self.SLEEP)
    

    async def put(self, item: T) -> None:       
        while True:
            try:
                self._Q.put_nowait(item)
                return None
            except Full:
                await sleep(self.SLEEP)


    def task_done(self) -> None:
        self._Q.task_done()
        return None

暫無
暫無

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

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