web-dev-qa-db-fra.com

Comment attendre que seul le premier thread soit terminé en Python

L'exigence est de démarrer cinq threads et d'attendre uniquement dans le thread le plus rapide. Les cinq threads sont tous allés chercher les mêmes données dans 5 directions, et une suffit pour continuer le flux de contrôle.

En fait, je dois attendre le retour des deux premiers threads pour vérifier l'un par rapport à l'autre. Mais je suppose que si je sais attendre le plus rapide. Je peux comprendre comment attendre le deuxième tour le plus rapide.

On parle beaucoup de join(timeout), mais vous ne savez pas à l'avance lequel attendre (lequel appliquer join à l'avance).

15
Peer Gynt

Utiliser une file d'attente: chaque thread une fois terminé place le résultat dans la file d'attente. Il vous suffit ensuite de lire le nombre de résultats approprié et d'ignorer le reste:

#!python3.3
import queue    # For Python 2.x use 'import Queue as queue'
import threading, time, random

def func(id, result_queue):
    print("Thread", id)
    time.sleep(random.random() * 5)
    result_queue.put((id, 'done'))

def main():
    q = queue.Queue()
    threads = [ threading.Thread(target=func, args=(i, q)) for i in range(5) ]
    for th in threads:
        th.daemon = True
        th.start()

    result1 = q.get()
    result2 = q.get()

    print("Second result: {}".format(result2))

if __name__=='__main__':
    main()

Documentation pour Queue.get() (sans argument, cela équivaut à Queue.get(True, None):

Queue.get ([block [ timeout]])

Supprimer et renvoyer un élément de la file d'attente . Si le paramètre args optionnel block a pour valeur true et que le délai d'attente est défini sur None (valeur par défaut de ), Bloquez-le si nécessaire jusqu'à ce qu'un élément soit disponible. Si timeout est un nombre positif , Il bloque au maximum les secondes écoulées et lève l'exception Vide si aucun élément n'était disponible dans ce délai. Sinon, (Block is false), renvoie un élément s'il est immédiatement disponible, sinon Déclenche l'exception Empty (le délai d'attente est ignoré dans ce cas).

15
Duncan

Si vous avez une sorte de boucle de traitement dans vos threads, le code suivant les terminera quand on utilisera un threading.Event () :

def my_thread(stop_event):
    while not stop_event.is_set():
        # do stuff in a loop

        # some check if stuff is complete
        if stuff_complete:
            stop_event.set()
            break

def run_threads():
    # create a thread event
    a_stop_event = threading.Event()

    # spawn the threads
    for x in range(5):
        t = threading.Thread(target=my_thread, args=[a_stop_event])
        t.start()

    while not a_stop_event.is_set():
        # wait for an event
        time.sleep(0.1)

    print "At least one thread is done"

Si votre processus est "bon marché" ou si un seul thread de type requête-réponse (par exemple, une requête HTTP asynchrone), alors La réponse de Duncan est une bonne approche.

4
will-hart

Ou tout simplement garder une trace de tous les threads finis dans une liste et laisser le second thread terminer pour gérer tout ce qui est censé être fait, les listes Python sont threadsafe.

finished_threads = []
event = threading.Event()

def func():
   do_important_stuff()

   thisthread = threading.current_thread()
   finished_threads.append(thisthread)
   if len(finished_threads) > 1 and finished_threads[1] == thisthread:
       #yay we are number two!
       event.set()

for i in range(5):
    threading.Thread(target=func).start()

event.wait()
1
Fredrik Håård

La méthode de Duncan est probablement la meilleure et c'est ce que je recommanderais. J'ai été légèrement ennuyé par le manque d '"attendre que le prochain fil terminé soit terminé" avant, alors j'ai juste écrit ceci pour l'essayer. Semble travailler. Utilisez simplement MWThread à la place de threading.thread et vous obtiendrez cette nouvelle fonction wait_for_thread.

Les variables globales sont un peu klunky; une alternative serait d'en faire des variables de niveau classe. Mais si cela est caché dans un module (mwthread.py ou quoi que ce soit), ça devrait aller dans les deux cas.

#! /usr/bin/env python

# Example of how to "wait for" / join whichever threads is/are done,
# in (more or less) the order they're done.

import threading
from collections import deque

_monitored_threads = []
_exited_threads = deque()
_lock = threading.Lock()
_cond = threading.Condition(_lock)

class MWThread(threading.Thread):
    """
    multi-wait-able thread, or monitored-wait-able thread
    """
    def run(self):
        tid = threading.current_thread()
        try:
            with _lock:
                _monitored_threads.append(tid)
            super(MWThread, self).run()
        finally:
            with _lock:
                _monitored_threads.remove(tid)
                _exited_threads.append(tid)
                _cond.notifyAll()

def wait_for_thread(timeout=None):
    """
    Wait for some thread(s) to have finished, with optional
    timeout.  Return the first finished thread instance (which
    is removed from the finished-threads queue).

    If there are no unfinished threads this returns None
    without waiting.
    """
    with _cond:
        if not _exited_threads and _monitored_threads:
            _cond.wait(timeout)
        if _exited_threads:
            result = _exited_threads.popleft()
        else:
            result = None
    return result

def main():
    print 'testing this stuff'
    def func(i):
        import time, random
        sleeptime = (random.random() * 2) + 1
        print 'thread', i, 'starting - sleep for', sleeptime
        time.sleep(sleeptime)
        print 'thread', i, 'finished'

    threads = [MWThread(target=func, args=(i,)) for i in range(3)]
    for th in threads:
        th.start()
    i = 0
    while i < 3:
        print 'main: wait up to .5 sec'
        th = wait_for_thread(.5)
        if th:
            print 'main: got', th
            th.join()
            i += 1
        else:
            print 'main: timeout'
    print 'I think I collected them all'
    print 'result of wait_for_thread():'
    print wait_for_thread()

if __== '__main__':
    main()
1
torek

Vous pouvez utiliser un événement pour cela. Voir http://docs.python.org/2/library/threading.html#event-objects L'idée est que les threads de travail déclenchent un événement lorsqu'ils sont terminés. Le thread principal attend cet événement avant de continuer. Le thread de travail peut définir une variable (mutexée) pour s'identifier à l'événement.

1
Ludo