2015-03-20 29 views
20

Sto usando il nuovo modulo concurrent.futures (che ha anche un backport Python 2) per fare un semplice I/O multithread. Ho difficoltà a capire come eliminare in modo pulito le attività iniziate utilizzando questo modulo.Come uccidi Futures dopo che hanno iniziato?

controllare il seguente Python 2/3 script, che riproduce il comportamento che sto vedendo:

#!/usr/bin/env python 
from __future__ import print_function 

import concurrent.futures 
import time 


def control_c_this(): 
    with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor: 
     future1 = executor.submit(wait_a_bit, name="Jack") 
     future2 = executor.submit(wait_a_bit, name="Jill") 

     for future in concurrent.futures.as_completed([future1, future2]): 
      future.result() 

     print("All done!") 


def wait_a_bit(name): 
    print("{n} is waiting...".format(n=name)) 
    time.sleep(100) 


if __name__ == "__main__": 
    control_c_this() 

Anche se questo script è in esecuzione sembra impossibile uccidere in modo pulito utilizzando la normale tastiera di interrupt Control-C. Sono in esecuzione su OS X.

  • Su Python 2.7 Devo ricorrere a kill dalla riga di comando per terminare lo script. Control-C è appena ignorato.
  • Su Python 3.4, Control-C funziona se lo colpisci due volte, ma poi vengono scaricate molte tracce di stack strane.

La maggior parte della documentazione che ho trovato online parla di come eliminare in modo pulito i thread con il vecchio modulo threading. Nulla di tutto ciò sembra applicarsi qui.

E tutte le modalità previste nel modulo concurrent.futures per fermare cose (come Executor.shutdown() e Future.cancel()) funzionano solo quando i Futures non hanno ancora iniziato o sono completo, che è inutile in questo caso. Voglio interrompere immediatamente il futuro.

Il mio caso d'uso è semplice: quando l'utente preme Control-C, lo script dovrebbe uscire immediatamente come fa qualsiasi script ben educato. È tutto ció che voglio.

Quindi qual è il modo corretto per ottenere questo comportamento quando si utilizza concurrent.futures?

+0

La lettura di un [domanda relativa su Java] (http://stackoverflow.com/q/671049/877069), vedo che l'uccisione di un thread non è qualcosa che si fa normalmente, perché può rendere il vostro stato del programma incoerente . Nel mio caso non penso * questo è un problema poiché voglio solo che l'intero programma esca. C'è stato anche menzione di [impostazione di alcune variabili condivise] (http://stackoverflow.com/a/11387729/877069) che i thread possono leggere per sapere quando auto-terminare. Non sono sicuro se quell'approccio si ripercuota su Python. –

+0

Solo un avviso, Ctrl + Break funzionerà, anche se Ctrl + C non lo fa. – jedwards

+0

@jedwards - Con Python 2 sto provando Command +. (che è Control + Break su OS X a quanto pare) e non sembra funzionare. Sembra essere equivalente a Control + C, in realtà. –

risposta

12

È un po 'doloroso. In sostanza, i thread di lavoro devono essere terminati prima che il thread principale possa uscire. Non puoi uscire a meno che non lo facciano. La soluzione alternativa tipica è avere uno stato globale, che ogni thread può controllare per determinare se devono fare più lavoro o meno.

Ecco lo quote che spiega perché. In sostanza, se i thread si chiudono quando l'interprete esegue, possono accadere cose brutte.

Ecco un esempio funzionante. Nota che C-c impiega al massimo 1 secondo per propagarsi perché la durata del sonno del thread secondario.

#!/usr/bin/env python 
from __future__ import print_function 

import concurrent.futures 
import time 
import sys 

quit = False 
def wait_a_bit(name): 
    while not quit: 
     print("{n} is doing work...".format(n=name)) 
     time.sleep(1) 

def setup(): 
    executor = concurrent.futures.ThreadPoolExecutor(max_workers=5) 
    future1 = executor.submit(wait_a_bit, "Jack") 
    future2 = executor.submit(wait_a_bit, "Jill") 

    # main thread must be doing "work" to be able to catch a Ctrl+C 
    # http://www.luke.maurits.id.au/blog/post/threads-and-signals-in-python.html 
    while (not (future1.done() and future2.done())): 
     time.sleep(1) 

if __name__ == "__main__": 
    try: 
     setup() 
    except KeyboardInterrupt: 
     quit = True 
+0

Prenderò per ora che questa è la soluzione migliore. Fa schifo che devi avere i tuoi thread masterizzare il tempo della CPU con il ciclo di sonno. Inoltre, quanto più reattivi vuoi che sia l'uccisione, tanto più corto deve essere il ciclo di sonno e quindi, più tempo di CPU viene bruciato su di esso. –

+1

Non devi dormire. Hai solo bisogno che controllino se devono uscire o meno. – cdosborn

+1

Questo trucco ha funzionato per me quando si utilizza 'ThreadPoolExecutor' ma non con' ProcessPoolExecutor'. C'è qualche trucco quando si tenta di condividere variabili globali tra processi? Dovrei conservare la bandiera 'quit' su disco o qualcosa del genere? –

Problemi correlati