web-dev-qa-db-fra.com

Pool de threads similaire au pool de multitraitement?

Existe-t-il une classe de pool pour les threads de travail , similaire au module de multitraitement classe de pool ?

J'aime par exemple le moyen facile de paralléliser une fonction de carte

def long_running_func(p):
    c_func_no_gil(p)

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

cependant, j'aimerais le faire sans la surcharge liée à la création de nouveaux processus.

Je connais le GIL. Toutefois, dans mon cas d'utilisation, la fonction sera une fonction C liée à l'IO pour laquelle l'encapsuleur python publiera le GIL avant l'appel de la fonction.

Dois-je écrire mon propre pool de threads?

309
Martin

Je viens de découvrir qu'il existe une interface de pool basée sur les threads dans le module multiprocessing, mais elle est quelque peu masquée et mal documentée. .

Il peut être importé via

_from multiprocessing.pool import ThreadPool
_

Il est implémenté à l'aide d'une classe de processus fictive qui encapsule un thread python. Cette classe de processus basée sur les threads se trouve dans multiprocessing.dummy , ce qui est brièvement mentionné dans docs . Ce module factice fournit supposément toute l'interface de multitraitement basée sur les threads.

413
Martin

Dans Python 3, vous pouvez utiliser concurrent.futures.ThreadPoolExecutor , c'est-à-dire:

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

Voir le docs pour plus d'informations et d'exemples.

192
Adrian Adamiak

Oui, et il semble avoir (plus ou moins) la même 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)
....
55
warfares

Pour quelque chose de très simple et léger (légèrement modifié à partir de ici ):

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 __== '__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()

Pour prendre en charge les rappels à la fin de la tâche, vous pouvez simplement ajouter le rappel à la tâche Tuple.

37
dgorissen

Bonjour, pour utiliser le pool de threads dans Python, vous pouvez utiliser cette bibliothèque:

from multiprocessing.dummy import Pool as ThreadPool

et pour l’utilisation, cette bibliothèque fait comme ça:

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

Les threads sont le nombre de threads que vous souhaitez et les tâches sont une liste des tâches les plus mappées au service.

7
Manochehr Rasouli

Voici le résultat que j'ai finalement utilisé. C'est une version modifiée des classes par dgorissen ci-dessus.

Fichier: 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)

Pour utiliser la piscine

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

Les frais généraux liés à la création de nouveaux processus sont minimes, en particulier lorsqu'il ne s'agit que de quatre d'entre eux. Je doute que ce soit un point chaud de performance de votre application. Restez simple, optimisez où vous devez et où pointent les résultats du profilage.

2
unbeli

Il n'y a pas de pool de threads intégré. Cependant, l'implémentation d'une file d'attente producteur/consommateur avec la classe Queue peut être très rapide.

De: 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
1
Yann Ramin