Cómo esperar hasta que solo se termine el primer hilo en Python

El requisito es iniciar cinco subprocesos y esperar solo en el subproceso más rápido. Los cinco hilos fueron a buscar las mismas 5 direcciones de datos, y una es suficiente para continuar el flujo de control.

En realidad, necesito esperar a que vuelvan los dos primeros hilos para verificar el uno contra el otro. Pero supongo que si sé esperar más rápido. Puedo averiguar cómo esperar al segundo más rápido.

Se habla mucho sobre join(timeout) , pero no se sabe de antemano cuál esperar (cuál aplicar para join por adelantado).

Use una cola: cada subproceso cuando finalice pone el resultado en la cola y luego solo necesita leer el número apropiado de resultados e ignorar el rest:

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

Documentación para Queue.get() (sin argumentos es equivalente a Queue.get(True, None) :

Queue.get ([bloque [, timeout]])

Eliminar y devolver un artículo de la cola. Si el bloque de argumentos opcional es verdadero y el tiempo de espera es Ninguno (el valor predeterminado), bloquee si es necesario hasta que haya un elemento disponible. Si el tiempo de espera es un número positivo, bloquea en la mayoría de los segundos de tiempo de espera y genera la excepción Vacío si no hay ningún elemento disponible dentro de ese tiempo. De lo contrario (el locking es falso), devuelva un elemento si hay uno disponible de inmediato, de lo contrario, genere la excepción Vacío (el tiempo de espera se ignora en ese caso).

Si tiene algún tipo de bucle de procesamiento en sus subprocesos, el siguiente código los terminará cuando uno termine utilizando un subproceso. Evento () :

 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 su proceso es “barato” o un único hilo de tipo solicitud-respuesta (es decir, por ejemplo, una solicitud HTTP asíncrona), entonces la respuesta de Duncan es un buen enfoque.

Puedes usar un evento para esto. Consulte http://docs.python.org/2/library/threading.html#event-objects. La idea es que los subprocesos de trabajo activen un evento cuando finalizan. El hilo principal espera este evento antes de continuar. El subproceso de trabajo puede establecer una variable (excluida) para identificarse con el evento.

O simplemente haga un seguimiento de todos los subprocesos terminados en una lista y deje que el segundo subproceso termine de manejar lo que se supone que se debe hacer. Las listas de Python son seguras para subprocesos.

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

El método de Duncan es probablemente el mejor y es lo que recomendaría. Sin embargo, antes me había molestado un poco la falta de “esperar a que se complete el siguiente hilo completado”, así que escribí esto para probarlo. Parece funcionar. Simplemente use MWThread en lugar de threading.thread y obtendrá esta nueva función wait_for_thread .

Las variables globales son un poco klunky; Una alternativa sería hacerlas variables de nivel de clase. Pero si esto está oculto en un módulo (mwthread.py o lo que sea), debería estar bien de cualquier manera.

 #! /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 __name__ == '__main__': main()