web-dev-qa-db-fra.com

Comment tuez Futures une fois qu'ils ont commencé?

J'utilise le nouveau module concurrent.futures (qui a également un backport Python 2) pour effectuer des E/S multithreads simples. J'ai des problèmes comprendre comment tuer proprement les tâches a commencé à utiliser ce module.

Consultez le script Python 2/3 suivant, qui reproduit le comportement que je vois:

#!/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()

Pendant l'exécution de ce script, il semble impossible de tuer proprement à l'aide de l'interruption régulière du clavier Control-C. Je suis sous OS X.

  • Sur Python 2.7 Je dois recourir à kill depuis la ligne de commande pour tuer le script. Control-C est juste ignoré.
  • On Python 3.4, Control-C fonctionne si vous le frappez deux fois, mais alors beaucoup de traces de pile étranges sont vidées.

La plupart de la documentation que j'ai trouvée en ligne explique comment tuer proprement les threads avec l'ancien module threading. Rien de tout cela ne semble s'appliquer ici.

Et toutes les méthodes fournies dans le module concurrent.futures Pour arrêter des choses (comme Executor.shutdown() et Future.cancel()) ne fonctionnent que lorsque les Futures n'ont pas encore commencé ou sont terminées, ce qui est inutile dans ce cas. Je veux interrompre l'avenir immédiatement.

Mon cas d'utilisation est simple: lorsque l'utilisateur frappe Control-C, le script doit se fermer immédiatement comme tout script bien comporté. C'est tout ce que je veux.

Alors, quelle est la bonne façon d'obtenir ce comportement lors de l'utilisation de concurrent.futures?

30
Nick Chammas

C'est un peu douloureux. Essentiellement, vos threads de travail doivent être terminés avant que votre thread principal puisse quitter. Vous ne pouvez pas quitter à moins qu'ils le fassent. La solution de contournement typique consiste à avoir un état global, que chaque thread peut vérifier pour déterminer s'il doit faire plus de travail ou non.

Voici le citation expliquant pourquoi. En substance, si les threads se terminaient lorsque l'interpréteur le faisait, de mauvaises choses pouvaient se produire.

Voici un exemple de travail. Notez que C-c prend au plus 1 seconde pour se propager en raison de la durée de veille du thread enfant.

#!/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
16
cdosborn