¿Cómo obtener el valor de retorno de un hilo?

15 minutos de lectura

avatar de usuario de wim
ingenio

La función foo a continuación devuelve una cadena 'foo'. como puedo sacar el valor 'foo' que se devuelve desde el objetivo del hilo?

from threading import Thread

def foo(bar):
    print('hello {}'.format(bar))
    return 'foo'

thread = Thread(target=foo, args=('world!',))
thread.start()
return_value = thread.join()

La “forma obvia de hacerlo”, que se muestra arriba, no funciona: thread.join() devuelto None.

avatar de usuario de kindall
tipo todo

Una forma que he visto es pasar un objeto mutable, como una lista o un diccionario, al constructor del hilo, junto con un índice u otro identificador de algún tipo. El subproceso puede almacenar sus resultados en su ranura dedicada en ese objeto. Por ejemplo:

def foo(bar, result, index):
    print 'hello {0}'.format(bar)
    result[index] = "foo"

from threading import Thread

threads = [None] * 10
results = [None] * 10

for i in range(len(threads)):
    threads[i] = Thread(target=foo, args=('world!', results, i))
    threads[i].start()

# do some other stuff

for i in range(len(threads)):
    threads[i].join()

print " ".join(results)  # what sound does a metasyntactic locomotive make?

si realmente quieres join() para devolver el valor de retorno de la función llamada, puede hacerlo con un Thread subclase como la siguiente:

from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)
    return "foo"

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs, Verbose)
        self._return = None
    def run(self):
        if self._Thread__target is not None:
            self._return = self._Thread__target(*self._Thread__args,
                                                **self._Thread__kwargs)
    def join(self):
        Thread.join(self)
        return self._return

twrv = ThreadWithReturnValue(target=foo, args=('world!',))

twrv.start()
print twrv.join()   # prints foo

Eso se vuelve un poco peliagudo debido a la manipulación de algunos nombres, y accede a estructuras de datos “privadas” que son específicas de Thread implementación… pero funciona.

Para Python 3:

class ThreadWithReturnValue(Thread):
    
    def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs={}, Verbose=None):
        Thread.__init__(self, group, target, name, args, kwargs)
        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args,
                                                **self._kwargs)
    def join(self, *args):
        Thread.join(self, *args)
        return self._return

  • genial, gracias por el ejemplo! Me pregunto por qué Thread no se implementó con el manejo de un valor de retorno en primer lugar, parece algo bastante obvio para admitir.

    – Wim

    3 de agosto de 2011 a las 1:28

  • Creo que esta debería ser la respuesta aceptada: el OP solicitó threadingno es una biblioteca diferente para probar, además la limitación del tamaño del grupo presenta un problema potencial adicional, que sucedió en mi caso.

    – domoarigato

    31 de enero de 2015 a las 20:05

  • En python3 esto regresa TypeError: __init__() takes from 1 to 6 positional arguments but 7 were given . ¿Alguna manera de arreglar eso?

    – GuySoft

    30/10/2016 a las 15:20

  • join tiene un parámetro de tiempo de espera que debe pasarse

    – Teivaz

    22 de agosto de 2018 a las 10:44

  • Advertencia para cualquiera que tenga la tentación de hacer el segundo de estos (el _Thread__target cosa). Hará que cualquier persona que intente portar su código a Python 3 lo odie hasta que descubra lo que ha hecho (debido al uso de funciones no documentadas que cambiaron entre 2 y 3). Documenta bien tu código.

    –Ben Taylor

    20 de noviembre de 2019 a las 9:06

Avatar de usuario de Jake Biesinger
jake biesinger

FWIW, el multiprocessing El módulo tiene una buena interfaz para esto usando el Pool clase. Y si desea apegarse a los hilos en lugar de a los procesos, puede usar el multiprocessing.pool.ThreadPool clase como reemplazo directo.

def foo(bar, baz):
  print 'hello {0}'.format(bar)
  return 'foo' + baz

from multiprocessing.pool import ThreadPool
pool = ThreadPool(processes=1)

async_result = pool.apply_async(foo, ('world', 'foo')) # tuple of args for foo

# do some other stuff in the main process

return_val = async_result.get()  # get the return value from your function.

  • @JakeBiesinger Mi punto es que estaba buscando una respuesta, cómo obtener una respuesta de Thread, vine aquí y la respuesta aceptada no responde a la pregunta indicada. Diferencio hilos y procesos. Sé sobre Global Interpreter Lock, sin embargo, estoy trabajando en un problema de enlace de E / S, por lo que los subprocesos están bien, no necesito procesos. Otras respuestas aquí mejor respuesta a la pregunta indicada.

    – omikron

    19 de abril de 2015 a las 9:17

  • @omikron Pero los hilos en python no devuelven una respuesta a menos que use una subclase que habilite esta funcionalidad. De las posibles subclases, ThreadPools es una excelente opción (elija el número de subprocesos, use map/apply w/sync/async). A pesar de ser importado de multiprocessno tienen nada que ver con Procesos.

    –Jake Biesinger

    20 de abril de 2015 a las 2:17


  • @JakeBiesinger Oh, estoy ciego. Perdón por mis comentarios innecesarios. Tienes razón. Simplemente asumí que multiprocesamiento = procesos.

    – omikron

    20 de abril de 2015 a las 9:25

  • No olvides configurar processes=1 a mas de uno si tienes mas hilos!

    – imán

    16 de junio de 2015 a las 11:46

  • El problema con el multiprocesamiento y el grupo de subprocesos es que es mucho más lento configurar e iniciar subprocesos en comparación con la biblioteca básica de subprocesos. Es excelente para iniciar subprocesos de ejecución prolongada, pero anula el propósito cuando se necesita iniciar muchos subprocesos de ejecución corta. En mi opinión, la solución de usar “threading” y “Queue” documentada en otras respuestas aquí es una mejor alternativa para ese último caso de uso.

    – Yves Dorfman

    8 de enero de 2018 a las 14:41

Avatar de usuario de Ramarao Amara
Ramarao Amara

En Python 3.2+, stdlib concurrent.futures El módulo proporciona una API de nivel superior para threadingincluido el paso de valores devueltos o excepciones de un subproceso de trabajo al subproceso principal:

import concurrent.futures

def foo(bar):
    print('hello {}'.format(bar))
    return 'foo'

with concurrent.futures.ThreadPoolExecutor() as executor:
    future = executor.submit(foo, 'world!')
    return_value = future.result()
    print(return_value)

  • Para aquellos que se preguntan esto se puede hacer con una lista de hilos. futures = [executor.submit(foo, param) for param in param_list] Se mantendrá el orden, y saliendo del with permitirá la recogida de resultados. [f.result() for f in futures]

    – jayreed1

    4 de junio de 2020 a las 21:29


  • @ jayreed1 ese comentario merece una respuesta propia o debería incluirse en la respuesta. Muy útil.

    – Damián

    5 de agosto de 2020 a las 10:39

  • Wow… gracias por la respuesta, estaba buscando una solución de multiprocesamiento para mi código, pero esto me ayuda a hacerlo de una manera tan simple y el comentario de @jayreed1 lo hizo la guinda del pastel, gracias a todos…

    – Desarrollador

    27 de abril de 2021 a las 13:22

  • Muchas gracias, esto me ayudó a solucionar un problema que encontré en algunas bibliotecas que no son seguras para subprocesos. Me gustó tu respuesta a partir de ahí. Mis preguntas y respuestas: stackoverflow.com/questions/68982519/…

    – xCovelus

    30 de agosto de 2021 a las 10:50


  • Nunca he trabajado con esta biblioteca antes. ¿Tengo que cerrar el hilo de alguna manera para que no se “suelte”, o el ejecutor se encargará de eso automáticamente si solo uso el código como se muestra aquí?

    – Rumi P.

    22 de septiembre de 2021 a las 11:41

avatar de usuario de bj0
bj0

La respuesta de Jake es buena, pero si no desea utilizar un grupo de subprocesos (no sabe cuántos subprocesos necesitará, pero créelos según sea necesario), entonces una buena forma de transmitir información entre subprocesos es el incorporado Cola.Cola clase, ya que ofrece seguridad de subprocesos.

Creé el siguiente decorador para que actúe de manera similar al grupo de subprocesos:

def threaded(f, daemon=False):
    import Queue

    def wrapped_f(q, *args, **kwargs):
        '''this function calls the decorated function and puts the 
        result in a queue'''
        ret = f(*args, **kwargs)
        q.put(ret)

    def wrap(*args, **kwargs):
        '''this is the function returned from the decorator. It fires off
        wrapped_f in a new thread and returns the thread object with
        the result queue attached'''

        q = Queue.Queue()

        t = threading.Thread(target=wrapped_f, args=(q,)+args, kwargs=kwargs)
        t.daemon = daemon
        t.start()
        t.result_queue = q        
        return t

    return wrap

Entonces solo lo usas como:

@threaded
def long_task(x):
    import time
    x = x + 5
    time.sleep(5)
    return x

# does not block, returns Thread object
y = long_task(10)
print y

# this blocks, waiting for the result
result = y.result_queue.get()
print result

La función decorada crea un nuevo subproceso cada vez que se llama y devuelve un objeto Subproceso que contiene la cola que recibirá el resultado.

ACTUALIZAR

Ha pasado bastante tiempo desde que publiqué esta respuesta, pero aún recibe visitas, así que pensé en actualizarla para reflejar la forma en que hago esto en las versiones más nuevas de Python:

Python 3.2 añadido en el concurrent.futures módulo que proporciona una interfaz de alto nivel para tareas paralelas. Proporciona ThreadPoolExecutor y ProcessPoolExecutorpor lo que puede usar un subproceso o grupo de procesos con la misma API.

Una ventaja de esta API es que enviar una tarea a un Executor devuelve un Future objeto, que se completará con el valor de retorno del invocable que envíe.

Esto hace que adjuntar un queue objeto innecesario, lo que simplifica bastante al decorador:

_DEFAULT_POOL = ThreadPoolExecutor()

def threadpool(f, executor=None):
    @wraps(f)
    def wrap(*args, **kwargs):
        return (executor or _DEFAULT_POOL).submit(f, *args, **kwargs)

    return wrap

Esto usará un valor predeterminado módulo ejecutor de threadpool si no se pasa uno.

El uso es muy similar al anterior:

@threadpool
def long_task(x):
    import time
    x = x + 5
    time.sleep(5)
    return x

# does not block, returns Future object
y = long_task(10)
print y

# this blocks, waiting for the result
result = y.result()
print result

Si está usando Python 3.4+, una característica muy buena de usar este método (y los objetos Future en general) es que el futuro devuelto se puede envolver para convertirlo en un asyncio.Future con asyncio.wrap_future. Esto hace que funcione fácilmente con coroutines:

result = await asyncio.wrap_future(long_task(10))

Si no necesita acceder al subyacente concurrent.Future objeto, puede incluir la envoltura en el decorador:

_DEFAULT_POOL = ThreadPoolExecutor()

def threadpool(f, executor=None):
    @wraps(f)
    def wrap(*args, **kwargs):
        return asyncio.wrap_future((executor or _DEFAULT_POOL).submit(f, *args, **kwargs))

    return wrap

Luego, cada vez que necesite empujar el código de bloqueo o uso intensivo de la CPU fuera del subproceso del bucle de eventos, puede ponerlo en una función decorada:

@threadpool
def some_long_calculation():
    ...

# this will suspend while the function is executed on a threadpool
result = await some_long_calculation()

Avatar de usuario de Arik
Arik

Otra solución que no requiere cambiar su código existente:

import Queue             # Python 2.x
#from queue import Queue # Python 3.x

from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)     # Python 2.x
    #print('hello {0}'.format(bar))   # Python 3.x
    return 'foo'

que = Queue.Queue()      # Python 2.x
#que = Queue()           # Python 3.x

t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, 'world!'))
t.start()
t.join()
result = que.get()
print result             # Python 2.x
#print(result)           # Python 3.x

También se puede ajustar fácilmente a un entorno de subprocesos múltiples:

import Queue             # Python 2.x
#from queue import Queue # Python 3.x
from threading import Thread

def foo(bar):
    print 'hello {0}'.format(bar)     # Python 2.x
    #print('hello {0}'.format(bar))   # Python 3.x
    return 'foo'

que = Queue.Queue()      # Python 2.x
#que = Queue()           # Python 3.x

threads_list = list()

t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, 'world!'))
t.start()
threads_list.append

# Add more threads here
...
threads_list.append(t2)
...
threads_list.append(t3)
...

# Join all the threads
for t in threads_list:
    t.join()

# Check thread's return value
while not que.empty():
    result = que.get()
    print result         # Python 2.x
    #print(result)       # Python 3.x

  • t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, ‘world!’)) ¿Qué está haciendo q.put aquí, qué hace Queue.Queue()?

    – vijay shanker

    29/10/2016 a las 21:54

  • Para Python3, necesita cambiar a from queue import Queue.

    – Gino Mempín

    6 de febrero de 2019 a las 7:25


  • Este parece ser el método menos disruptivo (no es necesario reestructurar drásticamente la base del código original) para permitir que el valor de retorno regrese al hilo principal.

    – Fanchen Bao

    17 de diciembre de 2019 a las 23:07

  • @DaniyalWarraich Acabo de ejecutar ambos ejemplos con Python 3 y ambos funcionan de maravilla. Asegúrese de comentar o descomentar las líneas relevantes.

    – Arik

    9 de enero a las 13:02

  • @Jawad La lambda es el objetivo del hilo. Lambda tiene 2 argumentos: q y arg1. Luego pasamos dos argumentos a lambda: args=(que, 'world!') Eventualmente, arg1 es la entrada a la función foo(), world1 en este ejemplo.

    – Arik

    6 de mayo a las 15:24


La mayoría de las respuestas que he encontrado son largas y requieren estar familiarizado con otros módulos o funciones avanzadas de Python, y serán bastante confusas para alguien a menos que ya esté familiarizado con todo lo que dice la respuesta.

Código de trabajo para un enfoque simplificado:

import threading

class ThreadWithResult(threading.Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs={}, *, daemon=None):
        def function():
            self.result = target(*args, **kwargs)
        super().__init__(group=group, target=function, name=name, daemon=daemon)

Código de ejemplo:

import time, random


def function_to_thread(n):
    count = 0
    while count < 3:
            print(f'still running thread {n}')
            count +=1
            time.sleep(3)
    result = random.random()
    print(f'Return value of thread {n} should be: {result}')
    return result


def main():
    thread1 = ThreadWithResult(target=function_to_thread, args=(1,))
    thread2 = ThreadWithResult(target=function_to_thread, args=(2,))
    thread1.start()
    thread2.start()
    thread1.join()
    thread2.join()
    print(thread1.result)
    print(thread2.result)

main()

Explicación:
Quería simplificar las cosas significativamente, así que creé un ThreadWithResult clase y la heredó de threading.Thread. La función anidada function en __init__ llama a la función enhebrada de la que queremos guardar el valor y guarda el resultado de esa función anidada como el atributo de instancia self.result después de que el hilo termine de ejecutarse.

Crear una instancia de this es idéntico a crear una instancia de threading.Thread. Pase la función que desea ejecutar en un nuevo subproceso al target argumento y cualquier argumento que su función pueda necesitar para el args argumento y cualquier argumento de palabra clave para el kwargs argumento.

p.ej

my_thread = ThreadWithResult(target=my_function, args=(arg1, arg2, arg3))

Creo que esto es mucho más fácil de entender que la gran mayoría de las respuestas, ¡y este enfoque no requiere importaciones adicionales! incluí el time y random módulo para simular el comportamiento de un hilo, pero no son necesarios para lograr la funcionalidad solicitada en la pregunta original.

Sé que estoy respondiendo esto mucho tiempo después de que se hizo la pregunta, ¡pero espero que esto pueda ayudar a más personas en el futuro!


EDITAR: Creé el save-thread-result paquete PyPI para permitirle acceder al mismo código anterior y reutilizarlo en proyectos (El código de GitHub está aquí). El paquete PyPI amplía completamente el threading.Thread clase, por lo que puede establecer cualquier atributo que establecería en threading.thread sobre el ThreadWithResult clase también!

La respuesta original anterior repasa la idea principal detrás de esta subclase, pero para obtener más información, consulte el explicación más detallada (de la cadena de documentación del módulo) aquí.

Ejemplo de uso rápido:

pip3 install -U save-thread-result     # MacOS/Linux
pip  install -U save-thread-result     # Windows

python3     # MacOS/Linux
python      # Windows
from save_thread_result import ThreadWithResult

# As of Release 0.0.3, you can also specify values for
#`group`, `name`, and `daemon` if you want to set those
# values manually.
thread = ThreadWithResult(
    target = my_function,
    args   = (my_function_arg1, my_function_arg2, ...)
    kwargs = {my_function_kwarg1: kwarg1_value, my_function_kwarg2: kwarg2_value, ...}
)

thread.start()
thread.join()
if getattr(thread, 'result', None):
    print(thread.result)
else:
    # thread.result attribute not set - something caused
    # the thread to terminate BEFORE the thread finished
    # executing the function passed in through the
    # `target` argument
    print('ERROR! Something went wrong while executing this thread, and the function you passed in did NOT complete!!')

# seeing help about the class and information about the threading.Thread super class methods and attributes available:
help(ThreadWithResult)

  • t = Thread(target=lambda q, arg1: q.put(foo(arg1)), args=(que, ‘world!’)) ¿Qué está haciendo q.put aquí, qué hace Queue.Queue()?

    – vijay shanker

    29/10/2016 a las 21:54

  • Para Python3, necesita cambiar a from queue import Queue.

    – Gino Mempín

    6 de febrero de 2019 a las 7:25


  • Este parece ser el método menos disruptivo (no es necesario reestructurar drásticamente la base del código original) para permitir que el valor de retorno regrese al hilo principal.

    – Fanchen Bao

    17 de diciembre de 2019 a las 23:07

  • @DaniyalWarraich Acabo de ejecutar ambos ejemplos con Python 3 y ambos funcionan de maravilla. Asegúrese de comentar o descomentar las líneas relevantes.

    – Arik

    9 de enero a las 13:02

  • @Jawad La lambda es el objetivo del hilo. Lambda tiene 2 argumentos: q y arg1. Luego pasamos dos argumentos a lambda: args=(que, 'world!') Eventualmente, arg1 es la entrada a la función foo(), world1 en este ejemplo.

    – Arik

    6 de mayo a las 15:24


Avatar de usuario de la comunidad
Comunidad

Respuesta de Parris / Kindall join/return respuesta portada a Python 3:

from threading import Thread

def foo(bar):
    print('hello {0}'.format(bar))
    return "foo"

class ThreadWithReturnValue(Thread):
    def __init__(self, group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None):
        Thread.__init__(self, group, target, name, args, kwargs, daemon=daemon)

        self._return = None

    def run(self):
        if self._target is not None:
            self._return = self._target(*self._args, **self._kwargs)

    def join(self):
        Thread.join(self)
        return self._return


twrv = ThreadWithReturnValue(target=foo, args=('world!',))

twrv.start()
print(twrv.join())   # prints foo

Nota la Thread La clase se implementa de manera diferente en Python 3.

  • join toma un parámetro de tiempo de espera que debe pasarse

    – cz

    5 de enero de 2017 a las 11:57

  • la documentación establece que los únicos métodos para anular deben ser: __init__() y run() docs.python.org/3/library/threading.html#thread-objects

    – lmiguelmh

    6 ago 2021 a las 19:10


  • @lmiguelmh, esta es una continuación de una respuesta anterior que explica que sí, esto se mete con las partes internas.

    – ikegami

    26 oct a las 15:19

¿Ha sido útil esta solución?