Як використовувати багатопроцесорну чергу в Python?


94

Я маю багато проблем, намагаючись зрозуміти, як працює багатопроцесорна черга на python і як її реалізувати. Скажімо, у мене є два модулі python, які отримують доступ до даних із спільного файлу, давайте назвемо ці два модулі програмою запису та зчитування. Мій план полягає в тому, щоб і читач, і письменник розмістили запити у дві окремі багатопроцесорні черги, а потім третій процес вклав ці запити в цикл і виконав як такий.

Моя головна проблема полягає в тому, що я насправді не знаю, як правильно реалізувати multiprocessing.queue, ви не можете реально створити екземпляр об'єкта для кожного процесу, оскільки вони будуть окремими чергами. Як ви переконаєтесь, що всі процеси пов'язані із спільною чергою (або в цьому випадку черги)


4
передавати Черги кожному класу процесу як параметр, коли створюєте їх у батьківському процесі.
Джоел Корнетт,

Відповіді:


122

Моя головна проблема полягає в тому, що я насправді не знаю, як правильно реалізувати multiprocessing.queue, ви не можете реально створити екземпляр об'єкта для кожного процесу, оскільки вони будуть окремими чергами. Як ви переконаєтесь, що всі процеси пов'язані із спільною чергою (або в цьому випадку черги)

Це простий приклад читача та письменника, що ділиться однією чергою ... Письменник надсилає купу цілих чисел читачеві; коли у письменника закінчуються цифри, він надсилає "ГОТОВО", що дає читачеві зрозуміти, що він вирвався з циклу читання.

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 З документації до модуля багатопроцесорної роботи сказано, що Черга реалізована за допомогою декількох блокувань / семафорів. Отже, коли ви використовуєте методи черги get () і put (object) Queue, черга блокується, якщо якийсь інший процес / потік намагається щось отримати або поставити в чергу. Тому вам не доведеться турбуватися про те, щоб заблокувати його вручну.
almel

1
Явні умови зупинки кращі за умови неявної зупинки
Майк Пеннінгтон

2
Qsize може перейти до нуля, якщо читачі черги перевищують показник черги
Mike Pennington

8

у " from queue import Queue" немає модуля, який викликається queue, замість цього multiprocessingслід використовувати. Тому це має виглядати як " from multiprocessing import Queue"


11
Незважаючи на роки, користування multiprocessing.Queueправильним. Нормальний Queue.Queueвикористовується для потоків python . При спробі використовувати Queue.Queueз багатопроцесорною обробкою копії об'єкта Queue будуть створюватися в кожному дочірньому процесі, і дочірні процеси ніколи не оновлюватимуться. В основному, це Queue.Queueпрацює за допомогою глобального спільного об’єкта та multiprocessing.Queueпрацює за допомогою IPC. Див: stackoverflow.com/questions/925100 / ...
Майкл Guffre

5

Ось мертве використання простий з multiprocessing.Queueі multiprocessing.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

є фіктивним. Це призведе до вбивства працівника, навіть якщо через мілісекунди в чергу з’явиться інше завдання. Працівник не відновиться, і через деякий час ВСІ працівники зникнуть, оскільки вони випадково виявлять чергу на мить порожньою. Кінцевим результатом буде те, що основна функція багатопроцесорної обробки (та, що має з'єднання () на процесах) повернеться без завершення всіх завдань. Приємно. Успіху, налагоджуючи це, якщо у вас є тисячі завдань, а деякі відсутні.

Інше питання - використання вартових значень. Багато людей пропонують додати вартове значення в чергу, щоб позначити кінець черги. Але кому це позначити? Якщо є 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

Ми реалізували дві версії цього, один простий мульти нитка пул , який може виконувати багато видів викликаються об'єктів, що робить наше життя набагато простіше і другий варіант , який використовує процеси , що є менш гнучким з точки зору що викликаються об'єктів і вимагає і додатковий виклик на кріп.

Якщо встановити значення true_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

Щойно зробив простий і загальний приклад для демонстрації передачі повідомлення через Чергу між 2 автономними програмами. Він не відповідає безпосередньо на питання ОП, але повинен бути досить чітким із зазначенням концепції.

Сервер:

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')

введіть тут опис зображення

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.