web-dev-qa-db-fra.com

multithreading en python attend que tous les threads soient terminés

Cette question a peut-être été posée dans un contexte similaire, mais je n’étais pas capable de trouver une réponse après environ 20 minutes de recherche. Je vais donc poser la question.

J'ai écrit un script Python (disons: scriptA.py) et un script (disons scriptB.py)

Dans scriptB, je veux appeler scriptA plusieurs fois avec des arguments différents. Chaque fois, il faut environ une heure pour s'exécuter (son script est énorme, fait beaucoup de choses .. ne vous inquiétez pas pour ça) et je veux pouvoir exécuter le scriptA avec tous les différents arguments simultanément, mais je dois attendre que tous soient terminés avant de continuer; mon code:

import subprocess

#setup
do_setup()

#run scriptA
subprocess.call(scriptA + argumentsA)
subprocess.call(scriptA + argumentsB)
subprocess.call(scriptA + argumentsC)

#finish
do_finish()

Je veux exécuter tous les subprocess.call() en même temps, puis attendre qu'ils soient tous terminés, comment dois-je faire cela? 

J'ai essayé d'utiliser des threads comme dans l'exemple ici :

from threading import Thread
import subprocess

def call_script(args)
    subprocess.call(args)

#run scriptA   
t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))
t1.start()
t2.start()
t3.start()

Mais je ne pense pas que ce soit juste. 

Comment savoir s'ils ont tous fini de s'exécuter avant de passer à mon do_finish()?

83
Inbar Rose

Vous devez utiliser join method of Thread object à la fin du script.

t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))

t1.start()
t2.start()
t3.start()

t1.join()
t2.join()
t3.join()

Ainsi, le thread principal attendra que t1, t2 et t3 achèvent l'exécution.

117
Maksim Skurydzin

Placez les discussions dans une liste, puis utilisez la méthode Join

 threads = []

 t = Thread(...)
 threads.append(t)

 ...repeat as often as necessary...

 # Start all threads
 for x in threads:
     x.start()

 # Wait for all of them to finish
 for x in threads:
     x.join()
138
Aaron Digulla

Je préfère utiliser la compréhension de liste basée sur une liste de saisie:

inputs = [scriptA + argumentsA, scriptA + argumentsB, ...]
threads = [Thread(target=call_script, args=(i)) for i in inputs]
[t.start() for t in threads]
[t.join() for t in threads]
19
Adam Matan

En Python3, depuis Python 3.2, il existe une nouvelle approche pour atteindre le même résultat, que je préfère personnellement au processus traditionnel de création de thread/start/join, package concurrent.futures: https://docs.python.org/3/library/ concurrent.futures.html

En utilisant ThreadPoolExecutor, le code serait:

from concurrent.futures.thread import ThreadPoolExecutor
import time

def call_script(ordinal, arg):
    print('Thread', ordinal, 'argument:', arg)
    time.sleep(2)
    print('Thread', ordinal, 'Finished')

args = ['argumentsA', 'argumentsB', 'argumentsC']

with ThreadPoolExecutor(max_workers=2) as executor:
    ordinal = 1
    for arg in args:
        executor.submit(call_script, ordinal, arg)
        ordinal += 1
print('All tasks has been finished')

La sortie du code précédent est quelque chose comme:

Thread 1 argument: argumentsA
Thread 2 argument: argumentsB
Thread 1 Finished
Thread 2 Finished
Thread 3 argument: argumentsC
Thread 3 Finished
All tasks has been finished

L'un des avantages est que vous pouvez contrôler le débit en définissant le nombre maximal de travailleurs simultanés. 

16
Roberto

Vous pouvez avoir en classe quelque chose comme ci-dessous à partir duquel vous pouvez ajouter un nombre n de fonctions ou de scripts console_scrit à exécuter en mode parallèle, démarrer l’exécution et attendre que tous les travaux soient terminés. 

from multiprocessing import Process

class ProcessParallel(object):
    """
    To Process the  functions parallely

    """    
    def __init__(self, *jobs):
        """
        """
        self.jobs = jobs
        self.processes = []

    def fork_processes(self):
        """
        Creates the process objects for given function deligates
        """
        for job in self.jobs:
            proc  = Process(target=job)
            self.processes.append(proc)

    def start_all(self):
        """
        Starts the functions process all together.
        """
        for proc in self.processes:
            proc.start()

    def join_all(self):
        """
        Waits untill all the functions executed.
        """
        for proc in self.processes:
            proc.join()


def two_sum(a=2, b=2):
    return a + b

def multiply(a=2, b=2):
    return a * b


#How to run:
if __== '__main__':
    #note: two_sum, multiply can be replace with any python console scripts which
    #you wanted to run parallel..
    procs =  ProcessParallel(two_sum, multiply)
    #Add all the process in list
    procs.fork_processes()
    #starts  process execution 
    procs.start_all()
    #wait until all the process got executed
    procs.join_all()
5
PBD

Peut-être quelque chose comme

for t in threading.enumerate():
    if t.daemon:
        t.join()
2
jno

De la threadingdocumentation du module

Il y a un objet «thread principal»; cela correspond à l'initiale fil de contrôle dans le programme Python. Ce n'est pas un fil de démon.

Il est possible que des “objets de thread factices” soient créés . Ce sont des objets thread correspondant à des "threads extraterrestres", qui sont les threads de contrôle démarrés en dehors du module de threading, tels que directement à partir du code C. Les objets de thread factices ont une fonctionnalité limitée; ils sont toujours considérés comme vivants et démoniaques, et ne peuvent pas être join()ed . Ils ne sont jamais supprimés, car il est impossible de détecter le terminaison de threads extraterrestres.

Donc, pour attraper ces deux cas où vous n'êtes pas intéressé à garder une liste des threads que vous créez:

import threading as thrd


def alter_data(data, index):
    data[index] *= 2


data = [0, 2, 6, 20]

for i, value in enumerate(data):
    thrd.Thread(target=alter_data, args=[data, i]).start()

for thread in thrd.enumerate():
    if thread.daemon:
        continue
    try:
        thread.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err.args[0]:
            # catchs main thread
            continue
        else:
            raise

Après quoi:

>>> print(data)
[0, 4, 12, 40]
0
berna1111

Je viens de rencontrer le même problème où je devais attendre tous les threads créés à l'aide de la boucle for.J'ai juste essayé le code suivant. Ce n'est peut-être pas la solution parfaite, mais je pensais que ce serait une solution simple. tester:

for t in threading.enumerate():
    try:
        t.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err:
            continue
        else:
            raise
0
Omkar