il multithreading di python attende che tutti i thread siano terminati


119

Potrebbe essere stato chiesto in un contesto simile ma non sono riuscito a trovare una risposta dopo circa 20 minuti di ricerca, quindi chiederò.

Ho scritto uno script Python (diciamo: scriptA.py) e uno script (diciamo scriptB.py)

In scriptB voglio chiamare scriptA più volte con argomenti diversi, ogni volta impiega circa un'ora per essere eseguito, (è uno script enorme, fa molte cose .. non preoccuparti) e voglio essere in grado di eseguire il scriptA con tutti i diversi argomenti contemporaneamente, ma devo aspettare che TUTTI siano finiti prima di continuare; il mio codice:

import subprocess

#setup
do_setup()

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

#finish
do_finish()

Voglio correre tutto subprocess.call()allo stesso tempo, e poi aspettare che abbiano finito, come dovrei farlo?

Ho provato a utilizzare il threading come nell'esempio qui :

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

Ma non credo che sia giusto.

Come faccio a sapere che hanno finito tutti di correre prima di andare al mio do_finish()?

Risposte:


150

È necessario utilizzare il metodo di unioneThread dell'oggetto alla fine dello 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()

Così il thread principale sarà aspettare fino t1, t2e t3terminare l'esecuzione.


5
hmmm - hai problemi a capire qualcosa, non vuoi eseguire prima t1, aspettare fino alla fine, quindi andare su t2..etc, ecc.? come si fa a far accadere tutto in una volta? non vedo come questo li eseguirà allo stesso tempo?
Inbar Rose

25
La chiamata a joinblocca fino al termine dell'esecuzione del thread. Dovrai comunque attendere tutti i thread. Se t1finisce per primo inizierai ad aspettare t2(che potrebbe essere già finito e tu procederai immediatamente ad aspettare t3). Se ha t1impiegato più tempo per l'esecuzione, quando torni da esso entrambi t1e t2torneranno immediatamente senza blocco.
Maksim Skurydzin

1
non capisci la mia domanda - se copio il codice sopra nel mio codice - funzionerà? Oppure mi sfugge qualcosa?
Inbar Rose

2
okay vedo. ora capisco, ero un po 'confuso al riguardo ma penso di capire, in joinqualche modo collega il processo corrente al thread e aspetta che sia finito, e se t2 finisce prima di t1, quando t1 è finito controllerà che t2 sia finito vedi che sia, quindi controllare t3..etc..etc .. e solo quando tutto sarà finito continuerà. eccezionale.
Inbar Rose

3
diciamo che t1 richiede il tempo più lungo, ma t2 ha un'eccezione. cosa succede allora? puoi catturare quell'eccezione o controllare se t2 è finito bene o no?
Ciprian Tomoiagă

174

Metti i thread in un elenco e quindi usa il metodo 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()

1
Sì, funzionerebbe ma è più difficile da capire. Dovresti sempre cercare di trovare un equilibrio tra codice compatto e "leggibilità". Ricorda: il codice viene scritto una volta ma letto molte volte. Quindi è più importante che sia facile da capire.
Aaron Digulla

2
Il "modello di fabbrica" ​​non è qualcosa che posso spiegare in una frase. Cercalo su Google e cerca su stackoverflow.com. Ci sono molti esempi e spiegazioni. In poche parole: scrivi codice che crea qualcosa di complesso per te. Come una vera fabbrica: dai un ordine e ricevi indietro un prodotto finito.
Aaron Digulla

18
Non mi piace l'idea di utilizzare la comprensione della lista per i suoi effetti collaterali e non fare nulla di utile con la lista risultante. Un semplice ciclo for sarebbe più pulito anche se si estende su due righe ...
Ioan Alexandru Cucu,

1
@Aaron DIgull Lo capisco, quello che voglio dire è che vorrei semplicemente fare una comprensione delle liste for x in threads: x.join()piuttosto che usare
Ioan Alexandru Cucu

1
@IoanAlexandruCucu: sto ancora chiedendo se c'è una soluzione più leggibile ed efficiente: stackoverflow.com/questions/21428602/...
Aaron Digulla

29

In Python3, a partire da Python 3.2 c'è un nuovo approccio per raggiungere lo stesso risultato, che personalmente preferisco al tradizionale thread creation / start / join, package concurrent.futures: https://docs.python.org/3/library/concurrent.futures .html

Utilizzando un ThreadPoolExecutorcodice sarebbe:

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')

L'output del codice precedente è qualcosa del tipo:

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

Uno dei vantaggi è che puoi controllare la velocità effettiva impostando il numero massimo di lavoratori simultanei.


ma come puoi sapere quando tutti i thread nel threadpool sono terminati?
Prime By Design

1
Come puoi vedere nell'esempio, il codice dopo l' withistruzione viene eseguito quando tutte le attività sono terminate.
Roberto

questo non funziona. Prova a fare qualcosa di molto lungo nei thread. La tua istruzione di stampa verrà eseguita prima della fine del thread
Pranalee

@Pranalee, quel codice funziona, ho aggiornato il codice per aggiungere le linee di output. Non è possibile visualizzare "Tutte le attività ..." prima che tutti i thread siano terminati. In withquesto caso è così che funziona l' istruzione in base alla progettazione. Ad ogni modo, puoi sempre aprire una nuova domanda in SO e pubblicare il tuo codice in modo che possiamo aiutarti a scoprire cosa sta succedendo nel tuo caso.
Roberto

@PrimeByDesign puoi usare la concurrent.futures.waitfunzione, puoi vedere un esempio reale qui Documenti ufficiali: docs.python.org/3/library/…
Alexander Fortin

28

Preferisco usare la comprensione dell'elenco basata su un elenco di input:

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]

La risposta controllata spiega bene, ma questa è più breve e non richiede brutte ripetizioni. Solo una bella risposta. :)
tleb

La comprensione dell'elenco solo per gli effetti collaterali è generalmente ammortizzata *. Ma in questo caso d'uso, sembra essere una buona idea. * Stackoverflow.com/questions/5753597/...
Vinayak Kaniyarakkal

3
@VinayakKaniyarakkal for t in threads:t.start()non è meglio?
SmartManoj

5

Puoi avere una classe simile alla seguente dalla quale puoi aggiungere un numero 'n' di funzioni o console_scripts che desideri eseguire in parallelo, avviare l'esecuzione e attendere il completamento di tutti i lavori ..

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

Questo è multiprocessing. La domanda riguardava docs.python.org/3/library/threading.html
Rustam A.

3

Dalla threading documentazione del modulo

C'è un oggetto "thread principale"; questo corrisponde al thread di controllo iniziale nel programma Python. Non è un thread daemon.

Esiste la possibilità che vengano creati "oggetti thread fittizi". Questi sono oggetti thread corrispondenti a "thread alieni", che sono thread di controllo avviati al di fuori del modulo di threading, ad esempio direttamente dal codice C. Gli oggetti thread fittizi hanno funzionalità limitate; sono sempre considerati vivi e demoniaci e non possono essere modificati join(). Non vengono mai cancellati, poiché è impossibile rilevare la terminazione di thread alieni.

Quindi, per catturare quei due casi in cui non sei interessato a mantenere un elenco dei thread che crei:

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

Al che:

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

2

Forse qualcosa di simile

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

Ho provato questo codice ma non sono sicuro del suo funzionamento perché è stata stampata l'ultima istruzione del mio codice che era dopo questo ciclo for e ancora il processo non è stato terminato.
Omkar

1

Mi sono appena imbattuto nello stesso problema in cui dovevo aspettare tutti i thread che sono stati creati utilizzando il ciclo for. Ho appena provato il seguente pezzo di codice Potrebbe non essere la soluzione perfetta ma ho pensato che sarebbe stata una soluzione semplice testare:

for t in threading.enumerate():
    try:
        t.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err:
            continue
        else:
            raise
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.