Morto semplice esempio di utilizzo di Multiprocessing Queue, Pool e Locking


91

Ho provato a leggere la documentazione su http://docs.python.org/dev/library/multiprocessing.html ma sto ancora lottando con Queue, Pool e Locking multiprocessing. E per ora sono stato in grado di costruire l'esempio qui sotto.

Per quanto riguarda Queue and Pool, non sono sicuro di aver capito il concetto nel modo giusto, quindi correggimi se sbaglio. Quello che sto cercando di ottenere è elaborare 2 richieste alla volta (l'elenco dei dati ne ha 8 in questo esempio), quindi cosa dovrei usare? Pool per creare 2 processi in grado di gestire due diverse code (2 al massimo) o dovrei semplicemente usare Queue per elaborare 2 input ogni volta? Il blocco sarebbe quello di stampare correttamente le uscite.

import multiprocessing
import time

data = (['a', '2'], ['b', '4'], ['c', '6'], ['d', '8'],
        ['e', '1'], ['f', '3'], ['g', '5'], ['h', '7']
)


def mp_handler(var1):
    for indata in var1:
        p = multiprocessing.Process(target=mp_worker, args=(indata[0], indata[1]))
        p.start()


def mp_worker(inputs, the_time):
    print " Processs %s\tWaiting %s seconds" % (inputs, the_time)
    time.sleep(int(the_time))
    print " Process %s\tDONE" % inputs

if __name__ == '__main__':
    mp_handler(data)

Risposte:


129

La migliore soluzione per il tuo problema è utilizzare un file Pool. Usare Queues e avere una funzionalità separata di "alimentazione delle code" è probabilmente eccessivo.

Ecco una versione leggermente riorganizzata del tuo programma, questa volta con solo 2 processi collegati in un file Pool. Credo che sia il modo più semplice per procedere, con modifiche minime al codice originale:

import multiprocessing
import time

data = (
    ['a', '2'], ['b', '4'], ['c', '6'], ['d', '8'],
    ['e', '1'], ['f', '3'], ['g', '5'], ['h', '7']
)

def mp_worker((inputs, the_time)):
    print " Processs %s\tWaiting %s seconds" % (inputs, the_time)
    time.sleep(int(the_time))
    print " Process %s\tDONE" % inputs

def mp_handler():
    p = multiprocessing.Pool(2)
    p.map(mp_worker, data)

if __name__ == '__main__':
    mp_handler()

Si noti che la mp_worker()funzione ora accetta un singolo argomento (una tupla dei due argomenti precedenti) perché la map()funzione raggruppa i dati di input in sottoliste, ciascuna sottoelenco data come un singolo argomento alla funzione di lavoro.

Produzione:

Processs a  Waiting 2 seconds
Processs b  Waiting 4 seconds
Process a   DONE
Processs c  Waiting 6 seconds
Process b   DONE
Processs d  Waiting 8 seconds
Process c   DONE
Processs e  Waiting 1 seconds
Process e   DONE
Processs f  Waiting 3 seconds
Process d   DONE
Processs g  Waiting 5 seconds
Process f   DONE
Processs h  Waiting 7 seconds
Process g   DONE
Process h   DONE

Modifica come da commento @Thales di seguito:

Se vuoi "un blocco per ogni limite di pool" in modo che i tuoi processi vengano eseguiti in coppie tandem, ala:

A in attesa B in attesa | A fatto, B fatto | C in attesa, D in attesa | C fatto, D fatto | ...

quindi modificare la funzione del gestore per avviare i pool (di 2 processi) per ogni coppia di dati:

def mp_handler():
    subdata = zip(data[0::2], data[1::2])
    for task1, task2 in subdata:
        p = multiprocessing.Pool(2)
        p.map(mp_worker, (task1, task2))

Ora il tuo output è:

 Processs a Waiting 2 seconds
 Processs b Waiting 4 seconds
 Process a  DONE
 Process b  DONE
 Processs c Waiting 6 seconds
 Processs d Waiting 8 seconds
 Process c  DONE
 Process d  DONE
 Processs e Waiting 1 seconds
 Processs f Waiting 3 seconds
 Process e  DONE
 Process f  DONE
 Processs g Waiting 5 seconds
 Processs h Waiting 7 seconds
 Process g  DONE
 Process h  DONE

Grazie per l'esempio semplice e diretto di come farlo, ma come potrei applicare il blocco per ogni limite di pool? Voglio dire, se esegui il codice, vorrei vedere qualcosa del tipo "A in attesa B in attesa | A fatto, b fatto | C in attesa, D in attesa | C fatto, D fatto"
thclpr

2
In altre parole, non vuoi che C inizi finché sia ​​A che B non sono finiti?
Velimir Mlaker

Esatto, posso farlo usando multiprocessing.Process ma non riesco a capire come farlo usando pool
thclpr

Grazie mille, funziona come previsto, ma sulla funzione mp_handler stai facendo riferimento ai dati della variabile invece di var1 :)
thclpr

Va bene grazie, ho rimosso del var1tutto, riferendomi datainvece al globale .
Velimir Mlaker

8

Questo potrebbe non essere correlato al 100% alla domanda, ma nella mia ricerca di un esempio di utilizzo del multiprocessing con una coda questo viene visualizzato per primo su Google.

Questa è una classe di esempio di base che puoi istanziare e mettere gli elementi in una coda e puoi aspettare fino al termine della coda. È tutto ciò di cui avevo bisogno.

from multiprocessing import JoinableQueue
from multiprocessing.context import Process


class Renderer:
    queue = None

    def __init__(self, nb_workers=2):
        self.queue = JoinableQueue()
        self.processes = [Process(target=self.upload) for i in range(nb_workers)]
        for p in self.processes:
            p.start()

    def render(self, item):
        self.queue.put(item)

    def upload(self):
        while True:
            item = self.queue.get()
            if item is None:
                break

            # process your item here

            self.queue.task_done()

    def terminate(self):
        """ wait until queue is empty and terminate processes """
        self.queue.join()
        for p in self.processes:
            p.terminate()

r = Renderer()
r.render(item1)
r.render(item2)
r.terminate()

2
Cosa sono item1e item2? Sono una sorta di attività o funzioni che verranno eseguite in due processi diversi?
Zelphir Kaltstahl

2
sì, sono compiti o parametri di input che vengono elaborati in modo parallelo.
linqu

8

Ecco il mio goto personale per questo argomento:

Gist qui, (pull richieste benvenute!): Https://gist.github.com/thorsummoner/b5b1dfcff7e7fdd334ec

import multiprocessing
import sys

THREADS = 3

# Used to prevent multiple threads from mixing thier output
GLOBALLOCK = multiprocessing.Lock()


def func_worker(args):
    """This function will be called by each thread.
    This function can not be a class method.
    """
    # Expand list of args into named args.
    str1, str2 = args
    del args

    # Work
    # ...



    # Serial-only Portion
    GLOBALLOCK.acquire()
    print(str1)
    print(str2)
    GLOBALLOCK.release()


def main(argp=None):
    """Multiprocessing Spawn Example
    """
    # Create the number of threads you want
    pool = multiprocessing.Pool(THREADS)

    # Define two jobs, each with two args.
    func_args = [
        ('Hello', 'World',), 
        ('Goodbye', 'World',), 
    ]


    try:
        # Spawn up to 9999999 jobs, I think this is the maximum possible.
        # I do not know what happens if you exceed this.
        pool.map_async(func_worker, func_args).get(9999999)
    except KeyboardInterrupt:
        # Allow ^C to interrupt from any thread.
        sys.stdout.write('\033[0m')
        sys.stdout.write('User Interupt\n')
    pool.close()

if __name__ == '__main__':
    main()

1
Non sono esattamente sicuro che .map_async () sia migliore di .map () in qualche modo.
ThorSummoner

3
L'argomento a get()è un timeout, non ha nulla a che fare con il numero di lavori che vengono avviati.
mata

@mata quindi, è pensato per essere utilizzato in un ciclo di polling? .get(timeout=1)? e va bene dire solo .get()per ottenere l'elenco completo?
ThorSummoner

Sì, .get()attende indefinitamente fino a quando tutti i risultati sono disponibili e restituisce l'elenco dei risultati. È possibile utilizzare un ciclo di polling per verificare la disponibilità dei risultati meteorologici oppure è possibile passare una funzione di callback nella map_async()chiamata che verrà quindi richiamata per ogni risultato una volta che sarà disponibile.
mata

2

Per tutti coloro che utilizzano editor come Komodo Edit (win10) aggiungi sys.stdout.flush()a:

def mp_worker((inputs, the_time)):
    print " Process %s\tWaiting %s seconds" % (inputs, the_time)
    time.sleep(int(the_time))
    print " Process %s\tDONE" % inputs
    sys.stdout.flush()

o come prima riga per:

    if __name__ == '__main__':
       sys.stdout.flush()

Questo aiuta a vedere cosa succede durante l'esecuzione dello script; invece di dover guardare la casella nera della riga di comando.


1

Ecco un esempio dal mio codice (per pool threaded, ma cambia semplicemente il nome della classe e avrai un pool di processi):

def execute_run(rp): 
   ... do something 

pool = ThreadPoolExecutor(6)
for mat in TESTED_MATERIAL:
    for en in TESTED_ENERGIES:
        for ecut in TESTED_E_CUT:
            rp = RunParams(
                simulations, DEST_DIR,
                PARTICLE, mat, 960, 0.125, ecut, en
            )
            pool.submit(execute_run, rp)
pool.join()

Fondamentalmente:

  • pool = ThreadPoolExecutor(6) crea un pool per 6 thread
  • Quindi hai un sacco di for che aggiungono attività al pool
  • pool.submit(execute_run, rp) aggiunge un'attività al pool, il primo arogument è una funzione chiamata in un thread / processo, il resto degli argomenti viene passato alla funzione chiamata.
  • pool.join attende il completamento di tutte le attività.

2
Nota che stai usando concurrent.futures, ma l'OP chiede informazioni su multiprocessinge Python 2.7.
Tim Peters
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.