Pool di threading simile al pool di elaborazione multipla?


347

Esiste una classe Pool per thread di lavoro , simile alla classe Pool del modulo multiprocessing ?

Mi piace ad esempio il modo semplice di parallelizzare una funzione della mappa

def long_running_func(p):
    c_func_no_gil(p)

p = multiprocessing.Pool(4)
xs = p.map(long_running_func, range(100))

tuttavia vorrei farlo senza il sovraccarico di creare nuovi processi.

Conosco il GIL. Tuttavia, nel mio caso d'uso, la funzione sarà una funzione C associata a IO per la quale il wrapper python rilascerà il GIL prima della chiamata effettiva alla funzione.

Devo scrivere il mio pool di threading?


Ecco qualcosa che sembra promettente nel ricettario di
— otherchirps

1
Al giorno d'oggi è costruito-in: from multiprocessing.pool import ThreadPool.
— martineau,

Puoi approfondire questo I know about the GIL. However, in my usecase, the function will be an IO-bound C function for which the python wrapper will release the GIL before the actual function call.?
— mrgloom,

Risposte:


448

Ho appena scoperto che in realtà non v'è un'interfaccia pool di thread-based nel multiprocessingmodulo, ma è nascosto un po 'e non adeguatamente documentata.

Può essere importato tramite

from multiprocessing.pool import ThreadPool

È implementato usando una classe Process fittizia che avvolge un thread Python. Questa classe di processo basata su thread può essere trovata in multiprocessing.dummycui è menzionata brevemente nei documenti . Questo modulo fittizio fornisce presumibilmente l'intera interfaccia multiprocessore basata su thread.


5
È fantastico Ho riscontrato un problema durante la creazione di ThreadPool all'esterno del thread principale, ma puoi usarli da un thread figlio una volta creato. Ho inserito un problema: bugs.python.org/issue10015
— Olson

82
Non capisco perché questa classe non abbia documentazione. Tali classi di aiuto sono così importanti al giorno d'oggi.
— Wernight,

18
@Wernight: non è principalmente pubblico perché nessuno ha offerto una patch che lo fornisce (o qualcosa di simile) come threading.ThreadPool, inclusi documentazione e test. Sarebbe davvero una buona batteria da includere nella libreria standard, ma non accadrà se nessuno lo scrive. Un bel vantaggio di questa implementazione esistente in multiprocessing, è che dovrebbe rendere molto più semplice scrivere una tale patch di threading ( docs.python.org/devguide )
— ncoghlan,

3
@ daniel.gindi: multiprocessing.dummy.Pool/ multiprocessing.pool.ThreadPoolsono la stessa cosa e sono entrambi pool di thread. Imitano l' interfaccia di un pool di processi, ma sono implementati interamente in termini di threading. Rileggi i documenti, hai ottenuto al contrario.
— ShadowRanger,

9
@ daniel.gindi: continua a leggere : " multiprocessing.dummyreplica l'API di multiprocessingma non è altro che un wrapper attorno al threadingmodulo". multiprocessingin generale riguarda i processi, ma per consentire il passaggio tra processi e thread, hanno (principalmente) replicato l' multiprocessingAPI multiprocessing.dummy, ma supportato da thread, non da processi. L'obiettivo è consentire all'utente import multiprocessing.dummy as multiprocessingdi modificare il codice basato sul processo in thread.
— ShadowRanger,

236

In Python 3 puoi usare concurrent.futures.ThreadPoolExecutor, cioè:

executor = ThreadPoolExecutor(max_workers=10)
a = executor.submit(my_function)

Vedi i documenti per maggiori informazioni ed esempi.


6
per utilizzare il modulo di futures backported, eseguiresudo pip install futures
— yair il

è il modo più efficiente e veloce per l'elaborazione multipla
— Haritsinh Gohil,

2
Qual'è la differenza tra using ThreadPoolExecutore multiprocessing.dummy.Pool?
— Jay,

2
da concurrent.futures import ThreadPoolExecutor
— stackOverlord l'

63

Sì, e sembra avere (più o meno) la stessa API.

import multiprocessing

def worker(lnk):
    ....    
def start_process():
    .....
....

if(PROCESS):
    pool = multiprocessing.Pool(processes=POOL_SIZE, initializer=start_process)
else:
    pool = multiprocessing.pool.ThreadPool(processes=POOL_SIZE, 
                                           initializer=start_process)

pool.map(worker, inputs)
....

9
Il percorso di importazione per ThreadPoolè diverso da Pool. L'importazione corretta è from multiprocessing.pool import ThreadPool.
— Tagete

2
Stranamente questa non è un'API documentata e multiprocessing.pool è solo brevemente menzionato come fornente AsyncResult. Ma è disponibile in 2.xe 3.x.
— Marvin,

2
Questo è quello che stavo cercando. È solo una singola riga di importazione e una piccola modifica alla mia linea di pool esistente e funziona perfettamente.
— Danegrafici

39

Per qualcosa di molto semplice e leggero (leggermente modificato da qui ):

from Queue import Queue
from threading import Thread


class Worker(Thread):
    """Thread executing tasks from a given tasks queue"""
    def __init__(self, tasks):
        Thread.__init__(self)
        self.tasks = tasks
        self.daemon = True
        self.start()

    def run(self):
        while True:
            func, args, kargs = self.tasks.get()
            try:
                func(*args, **kargs)
            except Exception, e:
                print e
            finally:
                self.tasks.task_done()


class ThreadPool:
    """Pool of threads consuming tasks from a queue"""
    def __init__(self, num_threads):
        self.tasks = Queue(num_threads)
        for _ in range(num_threads):
            Worker(self.tasks)

    def add_task(self, func, *args, **kargs):
        """Add a task to the queue"""
        self.tasks.put((func, args, kargs))

    def wait_completion(self):
        """Wait for completion of all the tasks in the queue"""
        self.tasks.join()

if __name__ == '__main__':
    from random import randrange
    from time import sleep

    delays = [randrange(1, 10) for i in range(100)]

    def wait_delay(d):
        print 'sleeping for (%d)sec' % d
        sleep(d)

    pool = ThreadPool(20)

    for i, d in enumerate(delays):
        pool.add_task(wait_delay, d)

    pool.wait_completion()

Per supportare i callback al completamento dell'attività è sufficiente aggiungere il callback alla tupla dell'attività.


come possono mai unirsi i thread se si ripetono incondizionatamente infinitamente?
— Joseph Garvin,

@JosephGarvin L'ho provato e i thread continuano a bloccarsi su una coda vuota (poiché la chiamata a Queue.get()sta bloccando) fino al termine del programma, dopodiché vengono automaticamente chiusi.
— Forumulator

@JosephGarvin, bella domanda. Queue.join()si unirà effettivamente alla coda delle attività, non ai thread di lavoro. Quindi, quando la coda è vuota, wait_completionritorna, il programma termina e i thread vengono raccolti dal sistema operativo.
— randomir,

Se tutto questo codice è racchiuso in una funzione ordinata, non sembra fermare i thread anche quando la coda è vuota e pool.wait_completion()ritorna. Il risultato è che i thread continuano a costruire.
— ubiquibacon,

17

Ciao per usare il pool di thread in Python puoi usare questa libreria:

from multiprocessing.dummy import Pool as ThreadPool

e poi per l'uso, questa libreria fa così:

pool = ThreadPool(threads)
results = pool.map(service, tasks)
pool.close()
pool.join()
return results

I thread sono il numero di thread desiderati e le attività sono un elenco di attività che la maggior parte associa al servizio.


Grazie, questo è un grande suggerimento! Dai documenti: multiprocessing.dummy replica l'API del multiprocessing ma non è altro che un wrapper attorno al modulo threading. Una correzione - penso che tu voglia dire che l'API del pool è (funzione, iterabile)
— layser

2
Abbiamo perso le .close()e .join()chiamate e che le cause .map()di finire prima tutti i thread sono finiti. Solo un avvertimento.
— Anatoly Scherbakov,

8

Ecco il risultato che alla fine ho usato. È una versione modificata delle classi di dgorissen sopra.

File: threadpool.py

from queue import Queue, Empty
import threading
from threading import Thread


class Worker(Thread):
    _TIMEOUT = 2
    """ Thread executing tasks from a given tasks queue. Thread is signalable, 
        to exit
    """
    def __init__(self, tasks, th_num):
        Thread.__init__(self)
        self.tasks = tasks
        self.daemon, self.th_num = True, th_num
        self.done = threading.Event()
        self.start()

    def run(self):       
        while not self.done.is_set():
            try:
                func, args, kwargs = self.tasks.get(block=True,
                                                   timeout=self._TIMEOUT)
                try:
                    func(*args, **kwargs)
                except Exception as e:
                    print(e)
                finally:
                    self.tasks.task_done()
            except Empty as e:
                pass
        return

    def signal_exit(self):
        """ Signal to thread to exit """
        self.done.set()


class ThreadPool:
    """Pool of threads consuming tasks from a queue"""
    def __init__(self, num_threads, tasks=[]):
        self.tasks = Queue(num_threads)
        self.workers = []
        self.done = False
        self._init_workers(num_threads)
        for task in tasks:
            self.tasks.put(task)

    def _init_workers(self, num_threads):
        for i in range(num_threads):
            self.workers.append(Worker(self.tasks, i))

    def add_task(self, func, *args, **kwargs):
        """Add a task to the queue"""
        self.tasks.put((func, args, kwargs))

    def _close_all_threads(self):
        """ Signal all threads to exit and lose the references to them """
        for workr in self.workers:
            workr.signal_exit()
        self.workers = []

    def wait_completion(self):
        """Wait for completion of all the tasks in the queue"""
        self.tasks.join()

    def __del__(self):
        self._close_all_threads()


def create_task(func, *args, **kwargs):
    return (func, args, kwargs)

Per usare la piscina

from random import randrange
from time import sleep

delays = [randrange(1, 10) for i in range(30)]

def wait_delay(d):
    print('sleeping for (%d)sec' % d)
    sleep(d)

pool = ThreadPool(20)
for i, d in enumerate(delays):
    pool.add_task(wait_delay, d)
pool.wait_completion()

Annotion per altri lettori: questo codice è Python 3 (shebang #!/usr/bin/python3)
— Daniel Marschall,

Perché usi for i, d in enumerate(delays):e poi ignori il ivalore?
— martineau,

@martineau - probabilmente solo una reliquia dallo sviluppo in cui probabilmente volevano stampare idurante una corsa.
— n1k31t4,

Perché create_taskc'è? Cosa serve?
— MrR

Non posso credere e rispondere con 4 voti su SO è il modo di fare ThreadPooling in Python. Il Threadpool nella distribuzione ufficiale di Python è ancora rotto? Cosa mi sto perdendo?
— MrR

2

Il sovraccarico della creazione di nuovi processi è minimo, soprattutto quando sono solo 4 di essi. Dubito che questo sia un punto caldo delle prestazioni della tua applicazione. Mantienilo semplice, ottimizza dove devi e dove puntano i risultati della profilazione.


5
Se l'interrogatore si trova su Windows (cosa che non credo abbia specificato), penso che lo spinup del processo possa essere una spesa significativa. Almeno è sui progetti che ho fatto di recente. :-)
— Brandon Rhodes,

1

Non esiste un pool basato su thread incorporato. Tuttavia, può essere molto veloce implementare una coda produttore / consumatore con la Queueclasse.

Da: https://docs.python.org/2/library/queue.html

from threading import Thread
from Queue import Queue
def worker():
    while True:
        item = q.get()
        do_work(item)
        q.task_done()

q = Queue()
for i in range(num_worker_threads):
     t = Thread(target=worker)
     t.daemon = True
     t.start()

for item in source():
    q.put(item)

q.join()       # block until all tasks are done

3
Questo non è più il caso del concurrent.futuresmodulo.
— Thanatos,

11
Non penso più che sia vero. from multiprocessing.pool import ThreadPool
— Randall Hunt,


0

un altro modo può essere l'aggiunta del processo al pool di code del thread

import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=cpus) as executor:
    for i in range(0, len(list_of_files) - 1):
        a = executor.submit(loop_files2, i, list_of_files2, mt_list, temp_path, mt_dicto)
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.