web-dev-qa-db-fra.com

La bonne façon de limiter le nombre maximal de threads en cours d'exécution à la fois?

J'aimerais créer un programme qui exécute plusieurs threads légers, mais se limite à un nombre constant et prédéfini de tâches d'exécution simultanées, comme ceci (mais sans risque de condition de concurrence):

import threading

def f(arg):
    global running
    running += 1
    print("Spawned a thread. running=%s, arg=%s" % (running, arg))
    for i in range(100000):
        pass
    running -= 1
    print("Done")

running = 0
while True:
    if running < 8:
        arg = get_task()
        threading.Thread(target=f, args=[arg]).start()

Quel est le moyen le plus sûr/le plus rapide de mettre cela en œuvre?

35
d33tah

Il semble que vous souhaitiez mettre en œuvre le modèle producteur/consommateur avec huit travailleurs. Python a une classe Queue à cette fin, et elle est thread-safe.

Chaque opérateur doit appeler get() dans la file d'attente pour récupérer une tâche. Cet appel sera bloqué si aucune tâche n'est disponible, ce qui obligera le travailleur à rester inactif jusqu'à ce qu'il en devienne disponible. Ensuite, le travailleur doit exécuter la tâche et enfin appeler task_done() dans la file d'attente.

Pour placer des tâches dans la file, appelez put() dans la file.

À partir du thread principal, vous pouvez appeler join() dans la file d'attente pour attendre que toutes les tâches en attente soient terminées.

Cette approche présente l'avantage de ne pas créer ni détruire de threads, ce qui coûte cher. Les threads de travail s'exécuteront en continu, mais resteront endormis lorsqu'aucune tâche ne figure dans la file d'attente, en utilisant un temps processeur nul.

(La page de documentation liée contient un exemple de ce modèle.)

31
cdhowie

sémaphore est une variable ou un type de données abstrait utilisé pour contrôler l'accès à une ressource commune par plusieurs processus dans un système concurrent, tel qu'un système d'exploitation à multi-programmation; cela peut vous aider ici. 

threadLimiter = threading.BoundedSemaphore(maximumNumberOfThreads)

class MyThread(threading.Thread):

    def run(self):
        threadLimiter.acquire()
        try:
            self.Executemycode()
        finally:
            threadLimiter.release()

    def Executemycode(self):
        print(" Hello World!") 
        # <your code here>

De cette façon, vous pouvez facilement limiter le nombre de threads à exécuter simultanément pendant l'exécution du programme. La variable 'maximumNumberOfThreads' peut être utilisée pour définir une limite supérieure pour la valeur maximale des threads.

crédits

15
Hammad Haleem

Il serait beaucoup plus facile de l'implémenter en tant que pool de threads ou en tant qu'exécuteur, en utilisant multiprocessing.dummy.Pool ou concurrent.futures.ThreadPoolExecutor (ou, si vous utilisez Python 2.x, le port arrière futures ). Par exemple:

import concurrent

def f(arg):
    print("Started a task. running=%s, arg=%s" % (running, arg))
    for i in range(100000):
        pass
    print("Done")

with concurrent.futures.ThreadPoolExecutor(8) as executor:
    while True:
        arg = get_task()
        executor.submit(f, arg)

Bien sûr, si vous pouvez remplacer le get_task du modèle extrait par un get_tasks du modèle push qui, par exemple, génère des tâches une à la fois, la procédure est encore plus simple:

with concurrent.futures.ThreadPoolExecutor(8) as executor:
    for arg in get_tasks():
        executor.submit(f, arg)

Lorsque vous manquez de tâches (par exemple, get_task lève une exception ou get_tasks tourne à sec), cela indique automatiquement à l'exécuteur de s'arrêter après avoir vidé la file d'attente, attendu qu'elle s'arrête et tout nettoyer.

4
abarnert

J'ai vu cela le plus souvent écrit comme:

threads = [threading.Thread(target=f) for _ in range(8)]
for thread in threads:
    thread.start()
...
for thread in threads:
    thread.join()

Si vous souhaitez conserver un pool de threads en cours d'exécution traitant des tâches éphémères plutôt que de demander un nouveau travail, envisagez une solution construite autour des files d'attente, telle que " Comment attendre que seul le premier thread soit terminé en Python ".

3
Kirk Strauser

J'ai rencontré ce même problème et passé des jours (2 jours pour être précis) à trouver la bonne solution en utilisant une file d'attente. J'ai perdu une journée à parcourir le chemin ThreadPoolExecutor car il n'y a aucun moyen de limiter le nombre de threads lancés! Je lui ai alimenté une liste de 5 000 fichiers à copier et le code est devenu insensible une fois qu'il a été installé à environ 1 500 copies de fichiers simultanées. Le paramètre max_workers de ThreadPoolExecutor contrôle uniquement le nombre de travailleurs qui génèrent des threads et non le nombre de threads générés.

Ok, de toute façon, voici un exemple très simple d'utilisation d'une file d'attente pour cela:

import threading, time, random
from queue import Queue

jobs = Queue()

def do_stuff(q):
    while not q.empty():
        value = q.get()
        time.sleep(random.randint(1, 10))
        print(value)
        q.task_done()

for i in range(10):
    jobs.put(i)

for i in range(3):
    worker = threading.Thread(target=do_stuff, args=(jobs,))
    worker.start()

print("waiting for queue to complete", jobs.qsize(), "tasks")
jobs.join()
print("all done")
1
Paul Jacobs