Python에서 다중 처리 대기열을 사용하는 방법은 무엇입니까?


95

다중 처리 대기열이 파이썬에서 작동하는 방식과 구현 방법을 이해하는 데 많은 문제가 있습니다. 공유 파일에서 데이터에 액세스하는 두 개의 파이썬 모듈이 있다고 가정 해 보겠습니다.이 두 모듈을 작성자와 판독기라고합시다. 내 계획은 독자와 작성자 모두 요청을 두 개의 개별 다중 처리 대기열에 넣은 다음 세 번째 프로세스가 이러한 요청을 루프에서 팝하여 실행하도록하는 것입니다.

내 주요 문제는 multiprocessing.queue를 올바르게 구현하는 방법을 정말로 모른다는 것입니다. 각 프로세스에 대해 개체를 실제로 인스턴스화 할 수 없다는 것입니다. 개별 대기열이 될 것이기 때문입니다. 모든 프로세스가 공유 대기열과 관련이 있는지 확인하는 방법 (또는 이 경우 대기열)


4
상위 프로세스에서 인스턴스화 할 때 큐를 매개 변수로 각 프로세스 클래스에 전달하십시오.
Joel Cornett

답변:


122

내 주요 문제는 multiprocessing.queue를 올바르게 구현하는 방법을 정말로 모른다는 것입니다. 각 프로세스에 대해 개체를 실제로 인스턴스화 할 수 없다는 것입니다. 개별 대기열이 될 것이기 때문입니다. 모든 프로세스가 공유 대기열과 관련이 있는지 확인하는 방법 (또는 이 경우 대기열)

이것은 독자와 기록기가 단일 대기열을 공유하는 간단한 예입니다. 기록기는 독자에게 여러 정수를 보냅니다. 작성자가 숫자가 부족하면 'DONE'을 보내 독자가 읽기 루프를 벗어 났음을 알 수 있습니다.

from multiprocessing import Process, Queue
import time
import sys

def reader_proc(queue):
    ## Read from the queue; this will be spawned as a separate Process
    while True:
        msg = queue.get()         # Read from the queue and do nothing
        if (msg == 'DONE'):
            break

def writer(count, queue):
    ## Write to the queue
    for ii in range(0, count):
        queue.put(ii)             # Write 'count' numbers into the queue
    queue.put('DONE')

if __name__=='__main__':
    pqueue = Queue() # writer() writes to pqueue from _this_ process
    for count in [10**4, 10**5, 10**6]:             
        ### reader_proc() reads from pqueue as a separate process
        reader_p = Process(target=reader_proc, args=((pqueue),))
        reader_p.daemon = True
        reader_p.start()        # Launch reader_proc() as a separate python process

        _start = time.time()
        writer(count, pqueue)    # Send a lot of stuff to reader()
        reader_p.join()         # Wait for the reader to finish
        print("Sending {0} numbers to Queue() took {1} seconds".format(count, 
            (time.time() - _start)))

13
좋은 예입니다. OP의 혼란을 해결하기위한 추가 정보와 마찬가지로 ...이 예는 공유 큐가 마스터 프로세스에서 시작되어야하며 모든 하위 프로세스로 전달되어야 함을 보여줍니다. 완전히 관련되지 않은 두 프로세스가 데이터를 공유하려면 중앙 또는 관련 네트워크 장치 (예 : 소켓)를 통해 통신해야합니다. 정보를 조정해야합니다.
JDI

5
좋은 예 .. 나는 또한이 주제에 익숙하지 않습니다 .. 동일한 대상 함수를 실행하는 여러 프로세스가있는 경우 (다른 인수로) 데이터를 대기열에 넣는 동안 충돌하지 않는지 확인하는 방법 .. 잠금이 필요합니다 ?
WYSIWYG

@bharat_iyengar 다중 처리 모듈 문서에서 Queue는 몇 가지 잠금 / 세마포를 사용하여 구현되었다고 말합니다. 따라서 get () 및 put (object) Queue 메서드를 사용할 때 다른 프로세스 / 스레드가 큐에 무언가를 가져 오거나 넣으려고하면 큐가 차단됩니다. 따라서 수동으로 잠그는 것에 대해 걱정할 필요가 없습니다.
almel 2014-06-05

1
명시 정지 조건이 암시 정지 조건보다는 더 낫다
마이크 페닝 턴

2
큐 독자 큐 작가의 속도를 초과하는 경우 Qsize 0으로 갈 수 있습니다
마이크 페닝 턴에게

8

" from queue import Queue"에는라는 모듈이 없습니다 . queue대신 multiprocessing사용해야합니다. 따라서 " from multiprocessing import Queue" 처럼 보일 것입니다 .


11
몇 년 늦었지만 사용 multiprocessing.Queue은 정확합니다. 노멀 Queue.Queue은 파이썬 스레드에 사용 됩니다 . Queue.Queue다중 처리와 함께 사용하려고 하면 Queue 개체의 복사본이 각 하위 프로세스에 생성되고 하위 프로세스는 업데이트되지 않습니다. 기본적으로 Queue.Queue전역 공유 객체를 multiprocessing.Queue사용하여 작동하고 IPC를 사용하여 작동합니다. 참조 : stackoverflow.com/questions/925100/…
Michael Guffre

5

다음의 죽은 간단한 사용의 multiprocessing.Queuemultiprocessing.Process그 발신자가 별도의 프로세스로 "이벤트"플러스 인수를 보낼 수 있도록 그 파견하는 과정에 "do_"방법에 이벤트. (Python 3.4 이상)

import multiprocessing as mp
import collections

Msg = collections.namedtuple('Msg', ['event', 'args'])

class BaseProcess(mp.Process):
    """A process backed by an internal queue for simple one-way message passing.
    """
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.queue = mp.Queue()

    def send(self, event, *args):
        """Puts the event and args as a `Msg` on the queue
        """
       msg = Msg(event, args)
       self.queue.put(msg)

    def dispatch(self, msg):
        event, args = msg

        handler = getattr(self, "do_%s" % event, None)
        if not handler:
            raise NotImplementedError("Process has no handler for [%s]" % event)

        handler(*args)

    def run(self):
        while True:
            msg = self.queue.get()
            self.dispatch(msg)

용법:

class MyProcess(BaseProcess):
    def do_helloworld(self, arg1, arg2):
        print(arg1, arg2)

if __name__ == "__main__":
    process = MyProcess()
    process.start()
    process.send('helloworld', 'hello', 'world')

send(가), 부모 프로세스에서 발생하는 do_*자식 프로세스에서 발생합니다.

분명히 런 루프를 중단하고 자식 프로세스를 종료하는 예외 처리를 생략했습니다. run차단 또는 기타 제어 를 재정 의하여 사용자 정의 할 수도 있습니다 .

이것은 단일 작업자 프로세스가있는 상황에서만 유용하지만 좀 더 객체 지향적 인 일반적인 시나리오를 보여주기 위해이 질문에 대한 적절한 대답이라고 생각합니다.


1
뛰어난 답변! 감사합니다. +50 :)
kmiklas

3

큰 팬더 데이터 프레임을 전달하기 위해 대기열을 사용하여 다중 처리를 수행하는 방법을 설정하는 동안 스택 오버플로와 웹에서 여러 답변을 살펴 보았습니다. 모든 답변이 이와 같은 계산을 설정할 때 분명히 접하게 될 수많은 엣지 케이스를 고려하지 않고 동일한 종류의 솔루션을 반복하는 것처럼 보였습니다. 문제는 동시에 많은 것이 있다는 것입니다. 작업 수, 작업자 수, 각 작업 기간 및 작업 실행 중 가능한 예외. 이 모든 것들은 동기화를 어렵게 만들고 대부분의 답변은 어떻게 할 수 있는지를 다루지 않습니다. 그래서 이것은 몇 시간 동안 어슬렁 거리고 난 후에 제가 취한 것입니다. 바라건대 대부분의 사람들이 유용하다고 생각하기에 충분히 일반적 일 것입니다.

코딩 예제 전에 몇 가지 생각. queue.Empty또는 queue.qsize()기타 유사한 방법은 흐름 제어에 대해 신뢰할 수 없기 때문에 유사한 코드

while True:
    try:
        task = pending_queue.get_nowait()
    except queue.Empty:
        break

가짜입니다. 밀리 초 후에 다른 작업이 대기열에 표시 되더라도 작업자가 죽습니다. 작업자는 회복되지 않으며 잠시 후 모든 작업자는 무작위로 대기열이 일시적으로 비어 있음을 발견하므로 사라집니다. 최종 결과는 모든 작업이 완료되지 않은 상태에서 주요 다중 처리 함수 (프로세스에 join ()가있는 함수)가 반환됩니다. 좋은. 수천 개의 작업이 있고 일부가 누락 된 경우이를 통해 디버깅을 행운을 빌어 요.

다른 문제는 센티넬 값의 사용입니다. 많은 사람들이 대기열의 끝을 표시하기 위해 대기열에 센티넬 값을 추가 할 것을 제안했습니다. 그러나 정확히 누구에게 표시하려면? N 개의 작업자가있는 경우 N이 주거나받을 수있는 코어 수라고 가정하면 단일 센티넬 값은 한 작업자에게만 대기열의 끝을 표시합니다. 다른 모든 작업자는 남은 작업이 없을 때 더 많은 작업을 기다릴 것입니다. 내가 본 전형적인 예는

while True:
    task = pending_queue.get()
    if task == SOME_SENTINEL_VALUE:
        break

한 작업자는 센티널 값을 받고 나머지는 무기한 대기합니다. 내가 만난 게시물은 작업자가있는만큼 최소한 큐에 센티넬 값을 제출해야 모든 작업자가 얻을 수 있다고 언급 한 게시물이 없습니다.

다른 문제는 작업 실행 중 예외 처리입니다. 다시 이것들을 잡아서 관리해야합니다. 또한 completed_tasks대기열 이있는 경우 작업이 완료되기 전에 대기열에있는 항목 수를 결정적인 방식으로 독립적으로 계산해야합니다. 다시 대기열 크기에 의존하면 실패하고 예기치 않은 결과가 반환됩니다.

아래 예에서 par_proc()함수는 명명 된 인수 및 값과 함께 이러한 작업을 실행해야하는 함수를 포함한 작업 목록을받습니다.

import multiprocessing as mp
import dill as pickle
import queue
import time
import psutil

SENTINEL = None


def do_work(tasks_pending, tasks_completed):
    # Get the current worker's name
    worker_name = mp.current_process().name

    while True:
        try:
            task = tasks_pending.get_nowait()
        except queue.Empty:
            print(worker_name + ' found an empty queue. Sleeping for a while before checking again...')
            time.sleep(0.01)
        else:
            try:
                if task == SENTINEL:
                    print(worker_name + ' no more work left to be done. Exiting...')
                    break

                print(worker_name + ' received some work... ')
                time_start = time.perf_counter()
                work_func = pickle.loads(task['func'])
                result = work_func(**task['task'])
                tasks_completed.put({work_func.__name__: result})
                time_end = time.perf_counter() - time_start
                print(worker_name + ' done in {} seconds'.format(round(time_end, 5)))
            except Exception as e:
                print(worker_name + ' task failed. ' + str(e))
                tasks_completed.put({work_func.__name__: None})


def par_proc(job_list, num_cpus=None):

    # Get the number of cores
    if not num_cpus:
        num_cpus = psutil.cpu_count(logical=False)

    print('* Parallel processing')
    print('* Running on {} cores'.format(num_cpus))

    # Set-up the queues for sending and receiving data to/from the workers
    tasks_pending = mp.Queue()
    tasks_completed = mp.Queue()

    # Gather processes and results here
    processes = []
    results = []

    # Count tasks
    num_tasks = 0

    # Add the tasks to the queue
    for job in job_list:
        for task in job['tasks']:
            expanded_job = {}
            num_tasks = num_tasks + 1
            expanded_job.update({'func': pickle.dumps(job['func'])})
            expanded_job.update({'task': task})
            tasks_pending.put(expanded_job)

    # Use as many workers as there are cores (usually chokes the system so better use less)
    num_workers = num_cpus

    # We need as many sentinels as there are worker processes so that ALL processes exit when there is no more
    # work left to be done.
    for c in range(num_workers):
        tasks_pending.put(SENTINEL)

    print('* Number of tasks: {}'.format(num_tasks))

    # Set-up and start the workers
    for c in range(num_workers):
        p = mp.Process(target=do_work, args=(tasks_pending, tasks_completed))
        p.name = 'worker' + str(c)
        processes.append(p)
        p.start()

    # Gather the results
    completed_tasks_counter = 0
    while completed_tasks_counter < num_tasks:
        results.append(tasks_completed.get())
        completed_tasks_counter = completed_tasks_counter + 1

    for p in processes:
        p.join()

    return results

그리고 여기에 위의 코드를 실행하는 테스트가 있습니다.

def test_parallel_processing():
    def heavy_duty1(arg1, arg2, arg3):
        return arg1 + arg2 + arg3

    def heavy_duty2(arg1, arg2, arg3):
        return arg1 * arg2 * arg3

    task_list = [
        {'func': heavy_duty1, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
        {'func': heavy_duty2, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
    ]

    results = par_proc(task_list)

    job1 = sum([y for x in results if 'heavy_duty1' in x.keys() for y in list(x.values())])
    job2 = sum([y for x in results if 'heavy_duty2' in x.keys() for y in list(x.values())])

    assert job1 == 15
    assert job2 == 21

몇 가지 예외가있는 다른 하나

def test_parallel_processing_exceptions():
    def heavy_duty1_raises(arg1, arg2, arg3):
        raise ValueError('Exception raised')
        return arg1 + arg2 + arg3

    def heavy_duty2(arg1, arg2, arg3):
        return arg1 * arg2 * arg3

    task_list = [
        {'func': heavy_duty1_raises, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
        {'func': heavy_duty2, 'tasks': [{'arg1': 1, 'arg2': 2, 'arg3': 3}, {'arg1': 1, 'arg2': 3, 'arg3': 5}]},
    ]

    results = par_proc(task_list)

    job1 = sum([y for x in results if 'heavy_duty1' in x.keys() for y in list(x.values())])
    job2 = sum([y for x in results if 'heavy_duty2' in x.keys() for y in list(x.values())])

    assert not job1
    assert job2 == 21

도움이 되길 바랍니다.


2

우리는 이것의 두 가지 버전을 구현했습니다. 하나는 많은 유형의 콜 러블을 실행할 수 있는 간단한 다중 스레드 풀로, 우리의 삶을 훨씬 쉽게 만들어주고, 두 번째 버전 은 콜 러블 측면에서 덜 유연하고 딜에 대한 추가 호출을 필요로 하는 프로세스 를 사용 합니다 .

frozen_pool을 true로 설정하면 어느 클래스에서나 finish_pool_queue가 호출 될 때까지 실행이 중지됩니다.

스레드 버전 :

'''
Created on Nov 4, 2019

@author: Kevin
'''
from threading import Lock, Thread
from Queue import Queue
import traceback
from helium.loaders.loader_retailers import print_info
from time import sleep
import signal
import os

class ThreadPool(object):
    def __init__(self, queue_threads, *args, **kwargs):
        self.frozen_pool = kwargs.get('frozen_pool', False)
        self.print_queue = kwargs.get('print_queue', True)
        self.pool_results = []
        self.lock = Lock()
        self.queue_threads = queue_threads
        self.queue = Queue()
        self.threads = []

        for i in range(self.queue_threads):
            t = Thread(target=self.make_pool_call)
            t.daemon = True
            t.start()
            self.threads.append(t)

    def make_pool_call(self):
        while True:
            if self.frozen_pool:
                #print '--> Queue is frozen'
                sleep(1)
                continue

            item = self.queue.get()
            if item is None:
                break

            call = item.get('call', None)
            args = item.get('args', [])
            kwargs = item.get('kwargs', {})
            keep_results = item.get('keep_results', False)

            try:
                result = call(*args, **kwargs)

                if keep_results:
                    self.lock.acquire()
                    self.pool_results.append((item, result))
                    self.lock.release()

            except Exception as e:
                self.lock.acquire()
                print e
                traceback.print_exc()
                self.lock.release()
                os.kill(os.getpid(), signal.SIGUSR1)

            self.queue.task_done()

    def finish_pool_queue(self):
        self.frozen_pool = False

        while self.queue.unfinished_tasks > 0:
            if self.print_queue:
                print_info('--> Thread pool... %s' % self.queue.unfinished_tasks)
            sleep(5)

        self.queue.join()

        for i in range(self.queue_threads):
            self.queue.put(None)

        for t in self.threads:
            t.join()

        del self.threads[:]

    def get_pool_results(self):
        return self.pool_results

    def clear_pool_results(self):
        del self.pool_results[:]

프로세스 버전 :

  '''
Created on Nov 4, 2019

@author: Kevin
'''
import traceback
from helium.loaders.loader_retailers import print_info
from time import sleep
import signal
import os
from multiprocessing import Queue, Process, Value, Array, JoinableQueue, Lock,\
    RawArray, Manager
from dill import dill
import ctypes
from helium.misc.utils import ignore_exception
from mem_top import mem_top
import gc

class ProcessPool(object):
    def __init__(self, queue_processes, *args, **kwargs):
        self.frozen_pool = Value(ctypes.c_bool, kwargs.get('frozen_pool', False))
        self.print_queue = kwargs.get('print_queue', True)
        self.manager = Manager()
        self.pool_results = self.manager.list()
        self.queue_processes = queue_processes
        self.queue = JoinableQueue()
        self.processes = []

        for i in range(self.queue_processes):
            p = Process(target=self.make_pool_call)
            p.start()
            self.processes.append(p)

        print 'Processes', self.queue_processes

    def make_pool_call(self):
        while True:
            if self.frozen_pool.value:
                sleep(1)
                continue

            item_pickled = self.queue.get()

            if item_pickled is None:
                #print '--> Ending'
                self.queue.task_done()
                break

            item = dill.loads(item_pickled)

            call = item.get('call', None)
            args = item.get('args', [])
            kwargs = item.get('kwargs', {})
            keep_results = item.get('keep_results', False)

            try:
                result = call(*args, **kwargs)

                if keep_results:
                    self.pool_results.append(dill.dumps((item, result)))
                else:
                    del call, args, kwargs, keep_results, item, result

            except Exception as e:
                print e
                traceback.print_exc()
                os.kill(os.getpid(), signal.SIGUSR1)

            self.queue.task_done()

    def finish_pool_queue(self, callable=None):
        self.frozen_pool.value = False

        while self.queue._unfinished_tasks.get_value() > 0:
            if self.print_queue:
                print_info('--> Process pool... %s' % (self.queue._unfinished_tasks.get_value()))

            if callable:
                callable()

            sleep(5)

        for i in range(self.queue_processes):
            self.queue.put(None)

        self.queue.join()
        self.queue.close()

        for p in self.processes:
            with ignore_exception: p.join(10)
            with ignore_exception: p.terminate()

        with ignore_exception: del self.processes[:]

    def get_pool_results(self):
        return self.pool_results

    def clear_pool_results(self):
        del self.pool_results[:]
def test(eg):
        print 'EG', eg

다음 중 하나로 전화하십시오.

tp = ThreadPool(queue_threads=2)
tp.queue.put({'call': test, 'args': [random.randint(0, 100)]})
tp.finish_pool_queue()

또는

pp = ProcessPool(queue_processes=2)
pp.queue.put(dill.dumps({'call': test, 'args': [random.randint(0, 100)]}))
pp.queue.put(dill.dumps({'call': test, 'args': [random.randint(0, 100)]}))
pp.finish_pool_queue()

0

두 개의 독립 실행 형 프로그램 사이에서 큐를 통해 메시지를 전달하는 것을 보여주는 간단하고 일반적인 예제를 만들었습니다. OP의 질문에 직접 답하지는 않지만 개념을 나타낼만큼 명확해야합니다.

섬기는 사람:

multiprocessing-queue-manager-server.py

import asyncio
import concurrent.futures
import multiprocessing
import multiprocessing.managers
import queue
import sys
import threading
from typing import Any, AnyStr, Dict, Union


class QueueManager(multiprocessing.managers.BaseManager):

    def get_queue(self, ident: Union[AnyStr, int, type(None)] = None) -> multiprocessing.Queue:
        pass


def get_queue(ident: Union[AnyStr, int, type(None)] = None) -> multiprocessing.Queue:
    global q

    if not ident in q:
        q[ident] = multiprocessing.Queue()

    return q[ident]


q: Dict[Union[AnyStr, int, type(None)], multiprocessing.Queue] = dict()
delattr(QueueManager, 'get_queue')


def init_queue_manager_server():
    if not hasattr(QueueManager, 'get_queue'):
        QueueManager.register('get_queue', get_queue)


def serve(no: int, term_ev: threading.Event):
    manager: QueueManager
    with QueueManager(authkey=QueueManager.__name__.encode()) as manager:
        print(f"Server address {no}: {manager.address}")

        while not term_ev.is_set():
            try:
                item: Any = manager.get_queue().get(timeout=0.1)
                print(f"Client {no}: {item} from {manager.address}")
            except queue.Empty:
                continue


async def main(n: int):
    init_queue_manager_server()
    term_ev: threading.Event = threading.Event()
    executor: concurrent.futures.ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor()

    i: int
    for i in range(n):
        asyncio.ensure_future(asyncio.get_running_loop().run_in_executor(executor, serve, i, term_ev))

    # Gracefully shut down
    try:
        await asyncio.get_running_loop().create_future()
    except asyncio.CancelledError:
        term_ev.set()
        executor.shutdown()
        raise


if __name__ == '__main__':
    asyncio.run(main(int(sys.argv[1])))

고객:

multiprocessing-queue-manager-client.py

import multiprocessing
import multiprocessing.managers
import os
import sys
from typing import AnyStr, Union


class QueueManager(multiprocessing.managers.BaseManager):

    def get_queue(self, ident: Union[AnyStr, int, type(None)] = None) -> multiprocessing.Queue:
        pass


delattr(QueueManager, 'get_queue')


def init_queue_manager_client():
    if not hasattr(QueueManager, 'get_queue'):
        QueueManager.register('get_queue')


def main():
    init_queue_manager_client()

    manager: QueueManager = QueueManager(sys.argv[1], authkey=QueueManager.__name__.encode())
    manager.connect()

    message = f"A message from {os.getpid()}"
    print(f"Message to send: {message}")
    manager.get_queue().put(message)


if __name__ == '__main__':
    main()

용법

섬기는 사람:

$ python3 multiprocessing-queue-manager-server.py N

N생성해야하는 서버 수를 나타내는 정수입니다. <server-address-N>서버 의 출력 중 하나를 복사 하여 각 multiprocessing-queue-manager-client.py.

고객:

python3 multiprocessing-queue-manager-client.py <server-address-1>

결과

섬기는 사람:

Client 1: <item> from <server-address-1>

요점 : https://gist.github.com/89062d639e40110c61c2f88018a8b0e5


UPD : 여기 에 패키지를 만들었 습니다 .

섬기는 사람:

import ipcq


with ipcq.QueueManagerServer(address=ipcq.Address.DEFAULT, authkey=ipcq.AuthKey.DEFAULT) as server:
    server.get_queue().get()

고객:

import ipcq


client = ipcq.QueueManagerClient(address=ipcq.Address.DEFAULT, authkey=ipcq.AuthKey.DEFAULT)
client.get_queue().put('a message')

여기에 이미지 설명 입력

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.