7
votes

Comment laisser un fil de python finir gracieusement

Je fais un projet impliquant la collecte de données et la journalisation. J'ai 2 threads en cours d'exécution, un fil de collection et un thread de journalisation, les deux ont commencé à la main. J'essaie de permettre au programme d'être résilié gracieusement lorsque CTRL-C.

J'utilise un filetage.event pour signaler aux fils pour mettre fin à leurs boucles respectives. Cela fonctionne bien pour arrêter la méthode sim_collectdata , mais il ne semble pas être correctement arrêter le fil logData . La collection Collection Terminé L'instruction d'impression n'est jamais exécutée et que le programme vient de stalle. (Ça ne se termine pas, il suffit de rester là).

Le second tandis que boucle dans logData est de vous assurer que tout dans la file d'attente est enregistré. L'objectif est que CTRL-C arrête immédiatement le fil de collection, puis permettez au fil de journalisation de terminer la vidange de la file d'attente et de terminer uniquement complètement le programme. (En ce moment, les données sont simplement imprimées - finalement, il va être enregistré dans une base de données).

Je ne comprends pas pourquoi le deuxième fil ne se termine jamais. Je basse ce que j'ai fait sur cette réponse: Arrêt d'un fil après une certaine quantité de temps . Qu'est-ce que je manque? xxx


2 commentaires

@Tdelaney a la meilleure solution. Toutes les réponses de vote / délai d'attente sont médiocres.


Vous pouvez bien sûr, vous pouvez toujours ajouter un délai d'attente à INPUT_QUEUE.GET ()


4 Réponses :


1
votes

Vous appelez un blocage Obtenez sur votre INPUT_QUEUE CODE> sans délai. Dans l'une ou l'autre section de logData ​​code>, si vous appelez input_queue.get () code> et la file d'attente est vide, elle bloquera indéfiniment, empêchant le Logging_thread code> Atteindre l'achèvement.

Pour correction, vous voudrez appeler INPUT_QUEUE.GET_NOWAIT () code> ou transmettre un délai d'attente sur INPUT_QUEUE.get () code>. p>

Voici ma suggestion: P>

try:
    while True:
        time.sleep(10)
except (KeyboardInterrupt, SystemExit):
    stop_event.set()
    collection_thread.join()
    logging_thread.join()


2 commentaires

Yuck - Il n'y a pas besoin de sonder / dormir ici.


@TDelaney Vous avez probablement raison, et utilisez une valeur de délai d'attente sur obtenir pourrait être une meilleure façon de toute façon. Mais c'est comme ça que je l'ai jeté ensemble, il y a donc.



2
votes

Je ne suis pas un expert en threading, mais dans votre logData Fonction le premier d = INPUT_QUEUE.GET () bloque, c'est-à-dire si la file d'attente est vide. Il restera une attente pour toujours jusqu'à ce qu'un message de file d'attente soit reçu. C'est probablement pourquoi le fil logData ne se termine jamais, il est assis à attendre pour toujours pour un message de file d'attente.

Reportez-vous à la [Python Docs] pour modifier ceci en une file d'attente non bloquante Lire: Utilisez .get (false) ou .get_nowait () - mais nécessite une manipulation d'exception pour des cas lorsque la file d'attente est vide.


0 commentaires

9
votes

Le problème est que votre enregistreur attend sur d = INPUT_QUEUE.GET () code> et ne vérifiera pas l'événement. Une solution consiste à ignorer complètement l'événement et à inventer un message unique qui indique à l'enregistreur de s'arrêter. Lorsque vous recevez un signal, envoyez ce message à la file d'attente.

import threading
import Queue
import random
import time

def sim_collectData(input_queue, stop_event):
    ''' this provides some output simulating the serial
    data from the data logging hardware. 
    '''
    n = 0
    while not stop_event.is_set():
        input_queue.put("DATA: <here are some random data> " + str(n))
        stop_event.wait(random.randint(0,5))
        n += 1
    print "Terminating data collection..."
    input_queue.put(None)
    return

def logData(input_queue):
    n = 0

    # we *don't* want to loop based on queue size because the queue could
    # theoretically be empty while waiting on some data.
    while True:
        d = input_queue.get()
        if d is None:
            input_queue.task_done()
            return
        if d.startswith("DATA:"):
            print d
        input_queue.task_done()
        n += 1

def main():
    input_queue = Queue.Queue()

    stop_event = threading.Event() # used to signal termination to the threads

    print "Starting data collection thread...",
    collection_thread = threading.Thread(target=sim_collectData, args=(input_queue,     stop_event))
    collection_thread.start()
    print "Done."

    print "Starting logging thread...",
    logging_thread = threading.Thread(target=logData, args=(input_queue,))
    logging_thread.start()
    print "Done."

    try:
        while True:
            time.sleep(10)
    except (KeyboardInterrupt, SystemExit):
        # stop data collection. Let the logging thread finish logging everything in the queue
        stop_event.set()

main()


2 commentaires

Notez que, à la fin, vous devez mettre autant de Aucun dans la file d'attente car il existe des threads bloqués.


@canaaerus - c'est un très bon point. Dans ce cas, il n'y a que 1 fil de travailleur, mais appeler le fait que N threads de travailleur a besoin de N messages de terminaison est un bel addition.



0
votes

Basé sur la réponse de Tdelaney, j'ai créé une approche basée sur Itératrice. L'itérateur sort lorsque le message de terminaison est rencontré. J'ai également ajouté un compteur de combien de get -Calls sont en train de bloquer et un stop -method, qui envoie uniquement autant de messages de terminaison. Pour éviter une condition de race entre incrémentation et lecture du comptoir, je fixe un bit d'arrêt là-bas. En outre, je n'utilise pas Aucun comme message de terminaison, car il ne peut pas nécessairement être comparé à d'autres types de données lors de l'utilisation d'un PriorityQueue .

Il y a deux restrictions, que je n'avais pas besoin d'éliminer. Pour un Stop -method attend d'abord jusqu'à ce que la file d'attente soit vide avant de fermer les fils. La deuxième restriction est que je n'ai pas fait aucun code pour rendre la file d'attente réutilisable après stop . Ce dernier peut probablement être ajouté assez facilement, tandis que le précédent nécessite de faire attention à la concurrence et au contexte dans lequel le code est utilisé.

Vous devez décider si vous voulez que vous voulez arrêter à attendez également que tous les messages de terminaison soient consommés. J'ai choisi de mettre le nécessaire rejoindre là-bas, mais vous pouvez simplement le retirer.

Donc, c'est le code: xxx

Oh, et j'ai écrit cela dans Python 3.2 . Donc, après avoir réussi, xxx

vous l'utiliseriez comme xxx


0 commentaires