Python'da çoklu işlem kuyruğu nasıl kullanılır?


94

Çoklu işlem kuyruğunun python üzerinde nasıl çalıştığını ve nasıl uygulanacağını anlamaya çalışırken çok sorun yaşıyorum. Verilere paylaşılan bir dosyadan erişen iki python modülüm olduğunu varsayalım, bu iki modüle bir yazıcı ve bir okuyucu diyelim. Planım, hem okuyucunun hem de yazıcının istekleri iki ayrı çoklu işlem kuyruğuna koyması ve ardından üçüncü bir işlemin bu istekleri bir döngüde açıp bu şekilde yürütmesini sağlamaktır.

Benim asıl sorunum, multiprocessing.queue'u doğru bir şekilde nasıl uygulayacağımı gerçekten bilmememdir, her işlem için nesneyi gerçekten başlatamazsınız çünkü bunlar ayrı kuyruklar olacaktır, tüm işlemlerin paylaşılan bir kuyrukla (veya bu durumda kuyruklar)


4
Kuyrukları ana işlemde başlattığınızda her işlem sınıfına parametre olarak geçirin.
Joel Cornett

Yanıtlar:


122

Benim asıl sorunum, multiprocessing.queue'u doğru bir şekilde nasıl uygulayacağımı gerçekten bilmememdir, her işlem için nesneyi gerçekten başlatamazsınız çünkü bunlar ayrı kuyruklar olacaktır, tüm işlemlerin paylaşılan bir kuyrukla (veya bu durumda kuyruklar)

Bu, tek bir kuyruğu paylaşan bir okuyucu ve yazarın basit bir örneğidir ... Yazar okuyucuya bir grup tam sayı gönderir; yazarın sayıları bittiğinde, okuyucunun okuma döngüsünden çıkmasını bildiren 'DONE' gönderir.

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
Harika bir örnek. OP'nin kafa karışıklığını gidermek için ek bir bilgi parçası gibi ... Bu örnek, paylaşılan bir sıranın ana işlemden kaynaklanması gerektiğini ve daha sonra tüm alt işlemlerine aktarılması gerektiğini gösterir. Tamamen ilgisiz iki işlemin verileri paylaşması için, bazı merkezi veya ilişkili ağ aygıtı (örneğin soketler) üzerinden iletişim kurmaları gerekir. Bilgiyi bir şey koordine etmelidir.
jdi

5
güzel örnek .. ben de bu konuda yeniyim .. aynı hedef işlevi çalıştıran (farklı argümanlarla) birden fazla işlemim varsa, verileri kuyruğa koyarken bunların çakışmamasını nasıl sağlayabilirim .. kilit gereklidir ?
WYSIWYG

@bharat_iyengar Çoklu işlem modülü dokümantasyonundan, Kuyruğun birkaç kilit / semafor kullanılarak uygulandığını söylüyor. Bu nedenle, get () ve put (object) Queue yöntemlerini kullandığınızda, başka bir işlem / iş parçacığı kuyruğa bir şey almaya veya bir şey koymaya çalışıyorsa kuyruk engellenecektir. Yani manuel olarak kilitleme konusunda endişelenmenize gerek yok.
almel

1
Açık durdurma koşulları, örtük durdurma koşullarından daha iyidir
Mike Pennington

2
Sıra okuyucuları kuyruk yazarının oranını aşarsa Qsize sıfıra gidebilir
Mike Pennington

8

" from queue import Queue" içinde çağrılan modül yoktur queue, bunun yerine multiprocessingkullanılmalıdır. Bu nedenle " from multiprocessing import Queue" gibi görünmelidir


11
Yıllar geç iken kullanmak multiprocessing.Queuedoğrudur. Normal Queue.Queue, python iplikleri için kullanılır . Queue.QueueÇoklu işlemle kullanmayı denediğinizde , Queue nesnesinin kopyaları her bir alt süreçte oluşturulur ve alt işlemler asla güncellenmez. Temel olarak, Queue.Queueglobal paylaşılan bir nesne multiprocessing.Queuekullanarak çalışır ve IPC kullanarak çalışır. Bakınız: stackoverflow.com/questions/925100/…
Michael Guffre

5

İşte bir ölü basit kullanımı var multiprocessing.Queueve multiprocessing.Processbu Arayanların ayrı bir süreç için "olayı" artı argümanları göndermek için izin verdiğini gönderir süreci üzerinde "do_" yöntemine olay. (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)

Kullanım:

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, Üst süreçte gerçekleşir do_*alt süreçte gerçekleşir.

Açıkça çalışma döngüsünü kesintiye uğratacak ve alt süreçten çıkacak herhangi bir istisna işlemeyi bıraktım. runEngellemeyi veya başka herhangi bir şeyi kontrol etmek için geçersiz kılarak da özelleştirebilirsiniz .

Bu gerçekten yalnızca tek bir çalışan sürecinizin olduğu durumlarda yararlıdır, ancak biraz daha nesne yönelimli ortak bir senaryoyu göstermek için bu soruya uygun bir cevap olduğunu düşünüyorum.


1
Olağanüstü cevap! Teşekkür ederim. +50 :)
kmiklas

3

Büyük pandalar veri çerçevelerinin etrafından geçmek için kuyrukları kullanarak çoklu işlem yapmanın bir yolunu oluşturmaya çalışırken yığın taşması ve web üzerinden birden çok yanıta baktım. Bana öyle geliyordu ki, her yanıt, böyle hesaplamalar yapılırken kesinlikle karşılaşılabilecek çok sayıda uç durumu dikkate almadan aynı tür çözümleri tekrarlıyordu. Sorun şu ki, aynı anda birçok şey oynanıyor. Görevlerin sayısı, çalışan sayısı, her görevin süresi ve görevin yürütülmesi sırasındaki olası istisnalar. Bunların hepsi senkronizasyonu zorlaştırır ve çoğu cevap, nasıl devam edebileceğinize değinmez. Bu, birkaç saat uğraştıktan sonra aldığım şey, umarım bu çoğu insanın yararlı bulması için yeterince genel olacaktır.

Herhangi bir kodlama örneğinden önce bazı düşünceler. Yana queue.Emptyya da queue.qsize()ya da başka bir benzer bir yöntem akış kontrolü, herhangi bir kod gibi güvenilmez olduğu

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

sahte. Bu, milisaniyeler sonra kuyrukta başka bir görev ortaya çıksa bile çalışanı öldürecektir. Çalışan iyileşmeyecek ve bir süre sonra TÜM çalışanlar kuyruğu anlık olarak boş bulduklarından kaybolacaklar. Nihai sonuç, ana çoklu işlem işlevinin (işlemlerdeki join () işlevine sahip olan) tüm görevler tamamlanmadan geri döneceği olacaktır. Güzel. Binlerce göreviniz varsa ve birkaçı eksikse, bununla hata ayıklamada iyi şanslar.

Diğer sorun, sentinel değerlerin kullanılmasıdır. Birçok kişi kuyruğun sonunu işaretlemek için kuyruğa bir gözcü değeri eklemeyi önerdi. Ama tam olarak kime işaretlemek için? N sayıda çalışan varsa, N'nin verilen veya alınan çekirdek sayısı olduğu varsayılırsa, tek bir sentinel değer kuyruğun sonunu yalnızca bir çalışana işaretleyecektir. Diğer tüm işçiler, hiç kimse kalmadığında oturup daha fazla iş bekleyecekler. Gördüğüm tipik örnekler

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

Bir işçi sentinel değerini alırken geri kalanı süresiz olarak bekleyecektir. Karşılaştığım hiçbir gönderi, TÜM işçilerin alabilmesi için, nöbetçi değeri en az işçileriniz olduğu kadar kuyruğa göndermeniz gerektiğinden bahsetmedi.

Diğer sorun, görevin yürütülmesi sırasında istisnaların ele alınmasıdır. Yine bunlar yakalanmalı ve yönetilmelidir. Ayrıca, bir completed_taskskuyruğunuz varsa, işin tamamlanmasına karar vermeden önce kuyrukta kaç öğe olduğunu bağımsız olarak belirleyici bir şekilde saymanız gerekir. Yine sıra boyutlarına güvenmek başarısızlıkla sonuçlanır ve beklenmedik sonuçlar verir.

Aşağıdaki örnekte, par_proc()işlev, bu görevlerin adlandırılmış bağımsız değişkenler ve değerlerle birlikte yürütülmesi gereken işlevleri içeren bir görev listesi alacaktır.

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

Ve işte yukarıdaki kodu,

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

artı bazı istisnalar dışında bir tane daha

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

Umarım yardımcı olur.


2

Bunun iki versiyonunu uyguladık, biri hayatımızı çok daha kolay hale getiren birçok tipte çağrılabilen basit bir çoklu iş parçacığı havuzu ve çağrılabilirler açısından daha az esnek olan ve dereotu için ekstra çağrı gerektiren süreçleri kullanan ikinci versiyon .

Frozen_pool'u true olarak ayarlamak, her iki sınıfta da finish_pool_queue çağrılana kadar yürütmeyi dondurur.

Konu Versiyonu:

'''
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[:]

İşlem Sürümü:

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

Şunlardan biriyle arayın:

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

veya

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

Bir mesajın bir Kuyruk üzerinden 2 bağımsız program arasında geçirilmesini göstermek için basit ve genel bir örnek verdik. Doğrudan OP'nin sorusuna cevap vermez, ancak kavramı belirtecek kadar açık olmalıdır.

Sunucu:

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

Müşteri:

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

Kullanım

Sunucu:

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

Nkaç sunucunun oluşturulması gerektiğini belirten bir tamsayıdır. <server-address-N>Sunucunun çıktılarından birini kopyalayın ve her birinin ilk argümanı yapın multiprocessing-queue-manager-client.py.

Müşteri:

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

Sonuç

Sunucu:

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

Ana fikir: https://gist.github.com/89062d639e40110c61c2f88018a8b0e5


UPD : Burada bir paket oluşturdu .

Sunucu:

import ipcq


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

Müşteri:

import ipcq


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

görüntü açıklamasını buraya girin

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.