簡體   English   中英

multiprocessing.Process 和 asyncio 循環通信

[英]multiprocessing.Process and asyncio loop communication

import asyncio
from multiprocessing import Queue, Process
import time

task_queue = Queue()

# This is simulating the task
async def do_task(task_number):
  for progress in range(task_number):
    print(f'{progress}/{task_number} doing')
    await asyncio.sleep(10)

# This is the loop that accepts and runs tasks
async def accept_tasks():
  event_loop = asyncio.get_event_loop()
  while True:
    task_number = task_queue.get() <-- this blocks event loop from running do_task()
    event_loop.create_task(do_task(task_number))

# This is the starting point of the process,
# the event loop runs here
def worker():
  event_loop = asyncio.get_event_loop()
  event_loop.run_until_complete(accept_tasks())

# Run a new process
Process(target=worker).start()

# Simulate adding tasks every 1 second
for _ in range(1,50):
  task_queue.put(_)
  print('added to queue', _)
  time.sleep(1)

我正在嘗試運行一個單獨的進程,該進程運行一個事件循環來執行 I/O 操作。 現在,從父進程,我正在嘗試“排隊”任務。 問題是 do_task() 沒有運行。 唯一有效的解決方案是輪詢(即檢查是否為空,然后休眠 X 秒)。

經過一些研究,問題似乎是task_queue.get()沒有做事件循環友好的 IO。

aiopipe提供了一個解決方案,但假設兩個進程都在一個事件循環中運行。

我試着創造這個。 但是消費者沒有消費任何東西......

read_fd, write_fd = os.pipe()
consumer = AioPipeReader(read_fd)
producer = os.fdopen(write_fd, 'w')

這種情況的一個簡單解決方法是將task_number = task_queue.get()更改為task_number = await event_loop.run_in_executor(None, task_queue.get) 這樣阻塞的Queue.get()函數將被卸載到線程池並且當前的協程被掛起,作為一個好的 asyncio 公民。 同樣,一旦線程池完成該函數,協程將恢復執行。

這種方法是一種變通方法,因為它不能擴展到大量並發任務:每個阻塞調用“變成異步”都會占用線程池中的一個插槽,而那些超過池的最大工作線程數的調用甚至不會在一個 Threed 釋放之前開始執行。 例如,重寫所有 asyncio 以通過run_in_executor調用阻塞函數只會導致編寫錯誤的線程系統。 但是,如果您知道您有少量子進程,則使用run_in_executor是正確的,並且可以非常有效地解決問題。

我終於弄明白了。 有一種已知的方法可以使用aiopipe庫來做到這aiopipe 但是它可以在兩個不同進程的兩個事件循環上運行。 就我而言,我只有子進程運行事件循環。 為了解決這個問題,我使用open(fd, buffering=0)將寫入部分更改為無緩沖的正常寫入。

這是沒有任何庫的代碼。

import asyncio
from asyncio import StreamReader, StreamReaderProtocol
from multiprocessing import Process
import time
import os

# This is simulating the task
async def do_task(task_number):
  for progress in range(task_number):
    print(f'{progress}/{task_number} doing')
    await asyncio.sleep(1)

# This is the loop that accepts and runs tasks
async def accept_tasks(read_fd):
  loop = asyncio.get_running_loop()
  # Setup asynchronous reading
  reader = StreamReader()
  protocol = StreamReaderProtocol(reader)
  transport, _ = await loop.connect_read_pipe(
    lambda: protocol, os.fdopen(read_fd, 'rb', 0))

  while True:
      task_number = int(await reader.readline())
      await asyncio.sleep(1)
      loop.create_task(do_task(task_number))

  transport.close()

# This is the starting point of the process,
# the event loop runs here
def worker(read_fd):
  loop = asyncio.get_event_loop()
  loop.run_until_complete(accept_tasks(read_fd))

# Create read and write pipe
read_fd, write_fd = os.pipe()

# allow inheritance to child
os.set_inheritable(read_fd, True)
Process(target=worker, args=(read_fd, )).start()
# detach from parent
os.close(read_fd)

writer = os.fdopen(write_fd, 'wb', 0)
# Simulate adding tasks every 1 second
for _ in range(1,50):
  writer.write((f'{_}\n').encode())
  print('added to queue', _)
  time.sleep(1)

基本上,我們在子進程端使用異步讀取,在父進程端進行非緩沖同步寫入。 要執行前者,您需要連接事件循環,如accept_tasks協程中所示。

暫無
暫無

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

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