Multiprocessing: come utilizzare Pool.map su una funzione definita in una classe?


179

Quando corro qualcosa del tipo:

from multiprocessing import Pool

p = Pool(5)
def f(x):
     return x*x

p.map(f, [1,2,3])

funziona benissimo. Tuttavia, inserendo questo in funzione di una classe:

class calculate(object):
    def run(self):
        def f(x):
            return x*x

        p = Pool()
        return p.map(f, [1,2,3])

cl = calculate()
print cl.run()

Mi dà il seguente errore:

Exception in thread Thread-1:
Traceback (most recent call last):
  File "/sw/lib/python2.6/threading.py", line 532, in __bootstrap_inner
    self.run()
  File "/sw/lib/python2.6/threading.py", line 484, in run
    self.__target(*self.__args, **self.__kwargs)
  File "/sw/lib/python2.6/multiprocessing/pool.py", line 225, in _handle_tasks
    put(task)
PicklingError: Can't pickle <type 'function'>: attribute lookup __builtin__.function failed

Ho visto un post di Alex Martelli che trattava lo stesso tipo di problema, ma non era abbastanza esplicito.


1
"questo in funzione di una classe"? Puoi pubblicare il codice che effettivamente ottiene l'errore reale. Senza il codice reale possiamo solo indovinare cosa stai facendo di sbagliato.
S.Lott

Come osservazione generale, esistono moduli di decapaggio più potenti del modulo di decapaggio standard di Python (come il modulo picloud menzionato in questa risposta ).
klaus se

1
Ho avuto un problema simile con le chiusure IPython.Parallel, ma lì puoi aggirare il problema spingendo gli oggetti sui nodi. Sembra abbastanza fastidioso aggirare questo problema con il multiprocessing.
Alex S,

Qui calculateè serializzabili, così sembra come questo può essere risolto 1) creando un oggetto funzione con un costruttore che copia su un calculategrado e poi 2) passando un'istanza di questo oggetto funzione di Pool's mapmetodo. No?
11

1
@math Non credo che nessuno dei "recenti cambiamenti" di Python sarà di alcun aiuto. Alcune limitazioni del multiprocessingmodulo sono dovute al suo obiettivo di essere un'implementazione multipiattaforma e alla mancanza di una fork(2)chiamata di sistema simile a Windows. Se non ti interessa il supporto di Win32, potrebbe esserci una soluzione alternativa più semplice basata sul processo. Oppure, se siete pronti a utilizzare i thread, invece di processi, è possibile sostituire from multiprocessing import Poolcon from multiprocessing.pool import ThreadPool as Pool.
Aya,

Risposte:


69

Sono stato anche infastidito dalle restrizioni su quale tipo di funzioni pool.map poteva accettare. Ho scritto quanto segue per aggirare questo. Sembra funzionare, anche per l'uso ricorsivo di parmap.

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(pipe, x):
        pipe.send(f(x))
        pipe.close()
    return fun

def parmap(f, X):
    pipe = [Pipe() for x in X]
    proc = [Process(target=spawn(f), args=(c, x)) for x, (p, c) in izip(X, pipe)]
    [p.start() for p in proc]
    [p.join() for p in proc]
    return [p.recv() for (p, c) in pipe]

if __name__ == '__main__':
    print parmap(lambda x: x**x, range(1, 5))

1
Questo ha funzionato molto bene per me, grazie. Ho riscontrato un punto debole: ho provato a usare parmap su alcune funzioni che passavano attorno a un defaultdict e ho ottenuto nuovamente PicklingError. Non ho trovato una soluzione a questo, ho solo rielaborato il mio codice per non usare il defaultdict.
senza l'

2
Questo non funziona in Python 2.7.2 (impostazione predefinita, 12 giugno 2011, 15:08:59) [MSC v.1500 32 bit (Intel)] su win32
ubershmekel,

3
Questo funziona su Python 2.7.3 Aug 1,2012, 05:14:39. Questo non funziona su iterabili giganti -> provoca un errore OS: [Errno 24] Troppi file aperti a causa del numero di pipe che apre.
Eiyrioü von Kauyf,

Questa soluzione genera un processo per ogni elemento di lavoro. La soluzione di "klaus se" di seguito è più efficiente.
ypnos,

85

Non ho potuto usare i codici pubblicati finora perché i codici che utilizzano "multiprocessing.Pool" non funzionano con le espressioni lambda e i codici che non utilizzano "multiprocessing.Pool" generano tutti i processi quanti sono gli elementi di lavoro.

Ho adattato il codice st genera un numero predefinito di lavoratori e scorre l'elenco di input solo se esiste un lavoratore inattivo. Ho anche abilitato la modalità "daemon" per i lavoratori st ctrl-c funziona come previsto.

import multiprocessing


def fun(f, q_in, q_out):
    while True:
        i, x = q_in.get()
        if i is None:
            break
        q_out.put((i, f(x)))


def parmap(f, X, nprocs=multiprocessing.cpu_count()):
    q_in = multiprocessing.Queue(1)
    q_out = multiprocessing.Queue()

    proc = [multiprocessing.Process(target=fun, args=(f, q_in, q_out))
            for _ in range(nprocs)]
    for p in proc:
        p.daemon = True
        p.start()

    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [q_in.put((None, None)) for _ in range(nprocs)]
    res = [q_out.get() for _ in range(len(sent))]

    [p.join() for p in proc]

    return [x for i, x in sorted(res)]


if __name__ == '__main__':
    print(parmap(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8]))

2
Come faresti che una barra di avanzamento funzioni correttamente con questa parmapfunzione?
shockburner

2
Una domanda: ho usato questa soluzione ma ho notato che i processi Python che ho generato sono rimasti attivi nella memoria. Qualche idea veloce su come ucciderli quando esce la tua mappa?
CompEcon,

1
@ klaus-se So che siamo scoraggiati dal dire grazie nei commenti, ma la tua risposta è troppo preziosa per me, non ho potuto resistere. Vorrei
poterti

2
@greole passando (None, None)come ultimo elemento indica funche ha raggiunto la fine della sequenza di elementi per ogni processo.
aganders3

4
@deshtop: puoi con una taglia se hai abbastanza reputazione te stesso :-)
Segna il

57

Il multiprocessing e il decapaggio sono interrotti e limitati a meno che non si salti fuori dalla libreria standard.

Se si utilizza un fork di multiprocessingchiamato pathos.multiprocesssing, è possibile utilizzare direttamente classi e metodi di classe nelle mapfunzioni di multiprocessing . Questo perché dillviene utilizzato al posto di pickleo cPicklee dillpuò serializzare quasi tutto in Python.

pathos.multiprocessingfornisce anche una funzione di mappa asincrona ... e può mapfunzionare con più argomenti (ad es. map(math.pow, [1,2,3], [4,5,6]))

Vedi le discussioni: Cosa possono fare insieme multiprocessing e aneto?

e: http://matthewrocklin.com/blog/work/2013/12/05/Parallelism-and-Serialization

Gestisce persino il codice che hai scritto inizialmente, senza modifiche e dall'interprete. Perché fare qualcos'altro che è più fragile e specifico di un singolo caso?

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> class calculate(object):
...  def run(self):
...   def f(x):
...    return x*x
...   p = Pool()
...   return p.map(f, [1,2,3])
... 
>>> cl = calculate()
>>> print cl.run()
[1, 4, 9]

Ottieni il codice qui: https://github.com/uqfoundation/pathos

E, solo per mostrare un po 'di più di ciò che può fare:

>>> from pathos.multiprocessing import ProcessingPool as Pool
>>> 
>>> p = Pool(4)
>>> 
>>> def add(x,y):
...   return x+y
... 
>>> x = [0,1,2,3]
>>> y = [4,5,6,7]
>>> 
>>> p.map(add, x, y)
[4, 6, 8, 10]
>>> 
>>> class Test(object):
...   def plus(self, x, y): 
...     return x+y
... 
>>> t = Test()
>>> 
>>> p.map(Test.plus, [t]*4, x, y)
[4, 6, 8, 10]
>>> 
>>> res = p.amap(t.plus, x, y)
>>> res.get()
[4, 6, 8, 10]

1
pathos.multiprocessing ha anche una mappa asincrona ( amap) che consente l'uso di barre di avanzamento e altra programmazione asincrona.
Mike McKerns,

Mi piace pathos.multiprocessing, che può servire quasi a rimpiazzare la mappa non parallela mentre mi godo il multiprocessing. Ho un semplice wrapper di pathos.multiprocessing.map, in modo che sia più efficiente in termini di memoria quando elabora una grande struttura di dati di sola lettura su più core, vedi questo repository git .
Fashandge,

Sembra interessante, ma non si installa. Questo è il messaggio che pip dà:Could not find a version that satisfies the requirement pp==1.5.7-pathos (from pathos)
xApple

1
Sì. Non ho rilasciato da un po 'di tempo in quanto ho suddiviso la funzionalità in pacchetti separati e ho anche convertito in codice compatibile 2/3. Gran parte di quanto sopra è stato modulare in multiprocesscui è compatibile 2/3. Vedere stackoverflow.com/questions/27873093/... e pypi.python.org/pypi/multiprocess .
Mike McKerns,

3
@xApple: proprio come follow-up, pathosha avuto una nuova versione stabile ed è anche compatibile con 2.xe 3.x.
Mike McKerns,

40

Al momento non esiste una soluzione al tuo problema, per quanto ne so: la funzione assegnata map()deve essere accessibile tramite un'importazione del modulo. Ecco perché il codice di robert funziona: la funzione f()può essere ottenuta importando il seguente codice:

def f(x):
    return x*x

class Calculate(object):
    def run(self):
        p = Pool()
        return p.map(f, [1,2,3])

if __name__ == '__main__':
    cl = Calculate()
    print cl.run()

In realtà ho aggiunto una sezione "principale", perché segue i consigli per la piattaforma Windows ("Assicurati che il modulo principale possa essere importato in modo sicuro da un nuovo interprete Python senza causare effetti collaterali indesiderati").

Ho anche aggiunto una lettera maiuscola davanti a Calculate, in modo da seguire PEP 8 . :)


18

La soluzione di mrule è corretta ma ha un bug: se il bambino restituisce una grande quantità di dati, può riempire il buffer della pipe, bloccando quello del bambino pipe.send(), mentre il genitore sta aspettando che il bambino esca pipe.join(). La soluzione è leggere i dati del bambino prima di join()ingerirlo. Inoltre, il bambino dovrebbe chiudere l'estremità del tubo del genitore per evitare un deadlock. Il codice qui sotto lo risolve. Inoltre, tieni presente che ciò parmapcrea un processo per elemento in X. Una soluzione più avanzata consiste nell'utilizzare multiprocessing.cpu_count()per dividere Xin una serie di blocchi, quindi unire i risultati prima di tornare. Lo lascio come un esercizio al lettore per non rovinare la concisione della bella risposta di mrule. ;)

from multiprocessing import Process, Pipe
from itertools import izip

def spawn(f):
    def fun(ppipe, cpipe,x):
        ppipe.close()
        cpipe.send(f(x))
        cpipe.close()
    return fun

def parmap(f,X):
    pipe=[Pipe() for x in X]
    proc=[Process(target=spawn(f),args=(p,c,x)) for x,(p,c) in izip(X,pipe)]
    [p.start() for p in proc]
    ret = [p.recv() for (p,c) in pipe]
    [p.join() for p in proc]
    return ret

if __name__ == '__main__':
    print parmap(lambda x:x**x,range(1,5))

Come scegli il numero di processi?
patapouf_ai,

Tuttavia muore abbastanza rapidamente a causa dell'errore OSError: [Errno 24] Too many open files. Penso che ci debba essere una sorta di limite al numero di processi affinché funzioni correttamente ...
patapouf_ai

13

Ho anche lottato con questo. Avevo funzioni come membri di dati di una classe, come esempio semplificato:

from multiprocessing import Pool
import itertools
pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # Needed to do something like this (the following line won't work)
        return pool.map(self.f,list1,list2)  

Avevo bisogno di usare la funzione self.f in una chiamata Pool.map () all'interno della stessa classe e self.f non ha preso una tupla come argomento. Poiché questa funzione era incorporata in una classe, non mi era chiaro come scrivere il tipo di wrapper suggerito da altre risposte.

Ho risolto questo problema utilizzando un wrapper diverso che accetta una tupla / lista, in cui il primo elemento è la funzione e gli elementi rimanenti sono gli argomenti di quella funzione, chiamati eval_func_tuple (f_args). Usando questo, la linea problematica può essere sostituita da return pool.map (eval_func_tuple, itertools.izip (itertools.repeat (self.f), list1, list2)). Ecco il codice completo:

File: util.py

def add(a, b): return a+b

def eval_func_tuple(f_args):
    """Takes a tuple of a function and args, evaluates and returns result"""
    return f_args[0](*f_args[1:])  

File: main.py

from multiprocessing import Pool
import itertools
import util  

pool = Pool()
class Example(object):
    def __init__(self, my_add): 
        self.f = my_add  
    def add_lists(self, list1, list2):
        # The following line will now work
        return pool.map(util.eval_func_tuple, 
            itertools.izip(itertools.repeat(self.f), list1, list2)) 

if __name__ == '__main__':
    myExample = Example(util.add)
    list1 = [1, 2, 3]
    list2 = [10, 20, 30]
    print myExample.add_lists(list1, list2)  

L'esecuzione di main.py darà [11, 22, 33]. Sentiti libero di migliorare, ad esempio eval_func_tuple potrebbe anche essere modificato per accettare argomenti di parole chiave.

In un'altra nota, in un'altra risposta, la funzione "parmap" può essere resa più efficiente nel caso di più Processi rispetto al numero di CPU disponibili. Sto copiando una versione modificata di seguito. Questo è il mio primo post e non ero sicuro di dover modificare direttamente la risposta originale. Ho anche rinominato alcune variabili.

from multiprocessing import Process, Pipe  
from itertools import izip  

def spawn(f):  
    def fun(pipe,x):  
        pipe.send(f(x))  
        pipe.close()  
    return fun  

def parmap(f,X):  
    pipe=[Pipe() for x in X]  
    processes=[Process(target=spawn(f),args=(c,x)) for x,(p,c) in izip(X,pipe)]  
    numProcesses = len(processes)  
    processNum = 0  
    outputList = []  
    while processNum < numProcesses:  
        endProcessNum = min(processNum+multiprocessing.cpu_count(), numProcesses)  
        for proc in processes[processNum:endProcessNum]:  
            proc.start()  
        for proc in processes[processNum:endProcessNum]:  
            proc.join()  
        for proc,c in pipe[processNum:endProcessNum]:  
            outputList.append(proc.recv())  
        processNum = endProcessNum  
    return outputList    

if __name__ == '__main__':  
    print parmap(lambda x:x**x,range(1,5))         

8

Ho preso la risposta di klaus se e aganders3 e ho creato un modulo documentato più leggibile e contenuto in un file. Puoi semplicemente aggiungerlo al tuo progetto. Ha anche una barra di avanzamento opzionale!

"""
The ``processes`` module provides some convenience functions
for using parallel processes in python.

Adapted from http://stackoverflow.com/a/16071616/287297

Example usage:

    print prll_map(lambda i: i * 2, [1, 2, 3, 4, 6, 7, 8], 32, verbose=True)

Comments:

"It spawns a predefined amount of workers and only iterates through the input list
 if there exists an idle worker. I also enabled the "daemon" mode for the workers so
 that KeyboardInterupt works as expected."

Pitfalls: all the stdouts are sent back to the parent stdout, intertwined.

Alternatively, use this fork of multiprocessing: 
https://github.com/uqfoundation/multiprocess
"""

# Modules #
import multiprocessing
from tqdm import tqdm

################################################################################
def apply_function(func_to_apply, queue_in, queue_out):
    while not queue_in.empty():
        num, obj = queue_in.get()
        queue_out.put((num, func_to_apply(obj)))

################################################################################
def prll_map(func_to_apply, items, cpus=None, verbose=False):
    # Number of processes to use #
    if cpus is None: cpus = min(multiprocessing.cpu_count(), 32)
    # Create queues #
    q_in  = multiprocessing.Queue()
    q_out = multiprocessing.Queue()
    # Process list #
    new_proc  = lambda t,a: multiprocessing.Process(target=t, args=a)
    processes = [new_proc(apply_function, (func_to_apply, q_in, q_out)) for x in range(cpus)]
    # Put all the items (objects) in the queue #
    sent = [q_in.put((i, x)) for i, x in enumerate(items)]
    # Start them all #
    for proc in processes:
        proc.daemon = True
        proc.start()
    # Display progress bar or not #
    if verbose:
        results = [q_out.get() for x in tqdm(range(len(sent)))]
    else:
        results = [q_out.get() for x in range(len(sent))]
    # Wait for them to finish #
    for proc in processes: proc.join()
    # Return results #
    return [x for i, x in sorted(results)]

################################################################################
def test():
    def slow_square(x):
        import time
        time.sleep(2)
        return x**2
    objs    = range(20)
    squares = prll_map(slow_square, objs, 4, verbose=True)
    print "Result: %s" % squares

EDIT : Aggiunto il suggerimento @ alexander-mcfarlane e una funzione di test


un problema con la barra di avanzamento ... La barra misura solo in modo inefficiente la suddivisione del carico di lavoro tra i processori. Se il carico di lavoro è perfettamente diviso, tutti i processori saranno join()contemporaneamente e si otterrà un lampo di 100%completamento sul tqdmdisplay. L'unica volta che sarà utile è se ogni processore ha un carico di lavoro distorto
Alexander McFarlane,

1
spostare tqdm()per concludere: result = [q_out.get() for _ in tqdm(sent)]e funziona molto meglio - grande sforzo anche se lo apprezzo molto così +1
Alexander McFarlane

Grazie per quel consiglio, lo proverò e poi aggiornerò la risposta!
xApple

La risposta viene aggiornata e la barra di avanzamento funziona molto meglio!
xApple

8

So che questo è stato chiesto più di 6 anni fa, ma volevo solo aggiungere la mia soluzione, poiché alcuni dei suggerimenti sopra sembrano orribilmente complicati, ma la mia soluzione era in realtà molto semplice.

Tutto quello che dovevo fare era avvolgere la chiamata pool.map () in una funzione di supporto. Passare l'oggetto di classe insieme a args per il metodo come una tupla, che sembrava un po 'così.

def run_in_parallel(args):
    return args[0].method(args[1])

myclass = MyClass()
method_args = [1,2,3,4,5,6]
args_map = [ (myclass, arg) for arg in method_args ]
pool = Pool()
pool.map(run_in_parallel, args_map)

7

Le funzioni definite nelle classi (anche all'interno delle funzioni all'interno delle classi) in realtà non vengono messe in discussione. Tuttavia, questo funziona:

def f(x):
    return x*x

class calculate(object):
    def run(self):
        p = Pool()
    return p.map(f, [1,2,3])

cl = calculate()
print cl.run()

15
grazie, ma trovo un po 'sporco definire la funzione al di fuori della classe. La classe dovrebbe raggruppare tutto ciò di cui ha bisogno per raggiungere un determinato compito.
Mermoz,

3
@Memoz: "La classe dovrebbe raggruppare tutto ciò di cui ha bisogno" Davvero? Non riesco a trovare molti esempi di questo. La maggior parte delle classi dipende da altre classi o funzioni. Perché chiamare una dipendenza di classe "sporca"? Cosa c'è di sbagliato in una dipendenza?
S.Lott

Bene, la funzione non dovrebbe modificare i dati di classe esistenti, perché modifica la versione nell'altro processo, quindi potrebbe essere un metodo statico. Puoi selezionare un metodo statico: stackoverflow.com/questions/1914261/… O, per qualcosa di così banale, potresti usare un lambda.
Robert,

6

So che questa domanda è stata posta 8 anni e 10 mesi fa, ma voglio presentarti la mia soluzione:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @staticmethod
    def methodForMultiprocessing(x):
        print(x*x)

    def main(self):
        if __name__ == "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

Devi solo trasformare la tua classe in un metodo statico. Ma è anche possibile con un metodo di classe:

from multiprocessing import Pool

class Test:

    def __init__(self):
        self.main()

    @classmethod
    def methodForMultiprocessing(cls, x):
        print(x*x)

    def main(self):
        if __name__ == "__main__":
            p = Pool()
            p.map(Test.methodForMultiprocessing, list(range(1, 11)))
            p.close()

TestObject = Test()

Testato in Python 3.7.3


3

Ho modificato il metodo di klaus se perché mentre funzionava con me con piccoli elenchi, si bloccava quando il numero di elementi era ~ 1000 o superiore. Invece di inviare i lavori uno alla volta con la Nonecondizione di arresto, carico la coda di input tutti in una volta e lascio solo che i processi lo sgranocchino finché non è vuoto.

from multiprocessing import cpu_count, Queue, Process

def apply_func(f, q_in, q_out):
    while not q_in.empty():
        i, x = q_in.get()
        q_out.put((i, f(x)))

# map a function using a pool of processes
def parmap(f, X, nprocs = cpu_count()):
    q_in, q_out   = Queue(), Queue()
    proc = [Process(target=apply_func, args=(f, q_in, q_out)) for _ in range(nprocs)]
    sent = [q_in.put((i, x)) for i, x in enumerate(X)]
    [p.start() for p in proc]
    res = [q_out.get() for _ in sent]
    [p.join() for p in proc]

    return [x for i,x in sorted(res)]

Modifica: sfortunatamente ora sto riscontrando questo errore sul mio sistema: il limite di dimensione massima della coda di elaborazione multipla è 32767 , speriamo che le soluzioni alternative ci siano di aiuto.


1

Puoi eseguire il codice senza problemi se in qualche modo ignori manualmente l' Poologgetto dall'elenco di oggetti nella classe perché non è in picklegrado come dice l'errore. Puoi farlo con la __getstate__funzione (guarda anche qui ) come segue. L' Poologgetto cercherà di trovare le __getstate__e __setstate__funzioni e eseguirli se lo trova quando si esegue map, map_asyncecc:

class calculate(object):
    def __init__(self):
        self.p = Pool()
    def __getstate__(self):
        self_dict = self.__dict__.copy()
        del self_dict['p']
        return self_dict
    def __setstate__(self, state):
        self.__dict__.update(state)

    def f(self, x):
        return x*x
    def run(self):
        return self.p.map(self.f, [1,2,3])

Quindi fa:

cl = calculate()
cl.run()

ti darà l'output:

[1, 4, 9]

Ho testato il codice sopra in Python 3.x e funziona.


0

Non sono sicuro che questo approccio sia stato adottato, ma una soluzione che sto usando è:

from multiprocessing import Pool

t = None

def run(n):
    return t.f(n)

class Test(object):
    def __init__(self, number):
        self.number = number

    def f(self, x):
        print x * self.number

    def pool(self):
        pool = Pool(2)
        pool.map(run, range(10))

if __name__ == '__main__':
    t = Test(9)
    t.pool()
    pool = Pool(2)
    pool.map(run, range(10))

L'output dovrebbe essere:

0
9
18
27
36
45
54
63
72
81
0
9
18
27
36
45
54
63
72
81

0
class Calculate(object):
  # Your instance method to be executed
  def f(self, x, y):
    return x*y

if __name__ == '__main__':
  inp_list = [1,2,3]
  y = 2
  cal_obj = Calculate()
  pool = Pool(2)
  results = pool.map(lambda x: cal_obj.f(x, y), inp_list)

È possibile che si desideri applicare questa funzione per ogni diversa istanza della classe. Quindi ecco la soluzione anche per quello

class Calculate(object):
  # Your instance method to be executed
  def __init__(self, x):
    self.x = x

  def f(self, y):
    return self.x*y

if __name__ == '__main__':
  inp_list = [Calculate(i) for i in range(3)]
  y = 2
  pool = Pool(2)
  results = pool.map(lambda x: x.f(y), inp_list)

0

Ecco la mia soluzione, che penso sia un po 'meno hacker della maggior parte degli altri qui. È simile alla risposta di nightowl.

someclasses = [MyClass(), MyClass(), MyClass()]

def method_caller(some_object, some_method='the method'):
    return getattr(some_object, some_method)()

othermethod = partial(method_caller, some_method='othermethod')

with Pool(6) as pool:
    result = pool.map(othermethod, someclasses)

0

Da http://www.rueckstiess.net/research/snippets/show/ca1d7d90 e http://qingkaikong.blogspot.com/2016/12/python-parallel-method-in-class.html

Possiamo creare una funzione esterna e seminarla con l'oggetto self della classe:

from joblib import Parallel, delayed
def unwrap_self(arg, **kwarg):
    return square_class.square_int(*arg, **kwarg)

class square_class:
    def square_int(self, i):
        return i * i

    def run(self, num):
        results = []
        results = Parallel(n_jobs= -1, backend="threading")\
            (delayed(unwrap_self)(i) for i in zip([self]*len(num), num))
        print(results)

O senza joblib:

from multiprocessing import Pool
import time

def unwrap_self_f(arg, **kwarg):
    return C.f(*arg, **kwarg)

class C:
    def f(self, name):
        print 'hello %s,'%name
        time.sleep(5)
        print 'nice to meet you.'

    def run(self):
        pool = Pool(processes=2)
        names = ('frank', 'justin', 'osi', 'thomas')
        pool.map(unwrap_self_f, zip([self]*len(names), names))

if __name__ == '__main__':
    c = C()
    c.run()

0

Questa potrebbe non essere un'ottima soluzione, ma nel mio caso la risolvo in questo modo.

from multiprocessing import Pool

def foo1(data):
    self = data.get('slf')
    lst = data.get('lst')
    return sum(lst) + self.foo2()

class Foo(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def foo2(self):
        return self.a**self.b   

    def foo(self):
        p = Pool(5)
        lst = [1, 2, 3]
        result = p.map(foo1, (dict(slf=self, lst=lst),))
        return result

if __name__ == '__main__':
    print(Foo(2, 4).foo())

Ho dovuto passare selfalla mia funzione in quanto devo accedere agli attributi e alle funzioni della mia classe attraverso quella funzione. Questo funziona per me. Correzioni e suggerimenti sono sempre ben accetti.


0

Ecco una piastra di caldaia che ho scritto per l'utilizzo del pool di elaborazione multipla in python3, in particolare python3.7.7 è stato utilizzato per eseguire i test. Ho ottenuto le mie corse più veloci usando imap_unordered. Basta collegare il tuo scenario e provarlo. Puoi usare timeito solo time.time()per capire quale funziona meglio per te.

import multiprocessing
import time

NUMBER_OF_PROCESSES = multiprocessing.cpu_count()
MP_FUNCTION = 'starmap'  # 'imap_unordered' or 'starmap' or 'apply_async'

def process_chunk(a_chunk):
    print(f"processig mp chunk {a_chunk}")
    return a_chunk


map_jobs = [1, 2, 3, 4]

result_sum = 0

s = time.time()
if MP_FUNCTION == 'imap_unordered':
    pool = multiprocessing.Pool(processes=NUMBER_OF_PROCESSES)
    for i in pool.imap_unordered(process_chunk, map_jobs):
        result_sum += i
elif MP_FUNCTION == 'starmap':
    pool = multiprocessing.Pool(processes=NUMBER_OF_PROCESSES)
    try:
        map_jobs = [(i, ) for i in map_jobs]
        result_sum = pool.starmap(process_chunk, map_jobs)
        result_sum = sum(result_sum)
    finally:
        pool.close()
        pool.join()
elif MP_FUNCTION == 'apply_async':
    with multiprocessing.Pool(processes=NUMBER_OF_PROCESSES) as pool:
        result_sum = [pool.apply_async(process_chunk, [i, ]).get() for i in map_jobs]
    result_sum = sum(result_sum)
print(f"result_sum is {result_sum}, took {time.time() - s}s")

Nello scenario di cui sopra imap_unorderedsembra effettivamente funzionare il peggio per me. Prova il tuo caso e confrontalo sulla macchina su cui prevedi di eseguirlo. Leggi anche su Process Pools . Saluti!

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.