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.