La función de tiempo de espera mediante el uso de subprocesos en python no funciona

He encontrado un código que crea una función de tiempo de espera aquí , que parece no funcionar. El código completo de la prueba está a continuación:

def timeout(func, args=(), kwargs={}, timeout_duration=1, default=None): import threading class InterruptableThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) self.result = None def run(self): try: self.result = func(*args, **kwargs) except: self.result = default it = InterruptableThread() it.start() it.join(timeout_duration) if it.isAlive(): return default else: return it.result def foo(): while True: pass timeout(foo,timeout_duration=3) 

Comportamiento esperado: el código termina en 3 segundos. ¿Dónde está el problema?

Un hilo no puede matarlo con gracia, así que con su código actual, foo nunca termina. (Con thread.daemon = True el progtwig Python se cerrará cuando solo queden subprocesos del daemon, pero eso no le permite terminar foo sin también terminar el hilo principal).

Algunas personas han intentado usar señales para detener la ejecución, pero esto puede ser peligroso en algunos casos.

Si puedes modificar foo , hay muchas soluciones posibles. Por ejemplo, puedes buscar un threading.Event para salir del bucle while.

Pero si no puede modificar foo , puede ejecutarlo en un subproceso utilizando el módulo de multiprocessing , ya que a diferencia de los subprocesos, los subprocesos pueden terminarse. Aquí hay un ejemplo de cómo podría verse eso:

 import time import multiprocessing as mp def foo(x = 1): cnt = 1 while True: time.sleep(1) print(x, cnt) cnt += 1 def timeout(func, args = (), kwds = {}, timeout = 1, default = None): pool = mp.Pool(processes = 1) result = pool.apply_async(func, args = args, kwds = kwds) try: val = result.get(timeout = timeout) except mp.TimeoutError: pool.terminate() return default else: pool.close() pool.join() return val if __name__ == '__main__': print(timeout(foo, kwds = {'x': 'Hi'}, timeout = 3, default = 'Bye')) print(timeout(foo, args = (2,), timeout = 2, default = 'Sayonara')) 

rendimientos

 ('Hi', 1) ('Hi', 2) ('Hi', 3) Bye (2, 1) (2, 2) Sayonara 

Tenga en cuenta que esto también tiene algunas limitaciones.

  • los subprocesos reciben una copia de las variables de los procesos padres. Si modifica una variable en un subproceso, NO afectará el proceso principal. Si la función de su función necesita modificar variables, necesitará usar una variable compartida .

  • los argumentos (pasados ​​a través de argumentos) y las palabras clave ( kwds ) deben ser seleccionables.

  • Los procesos son más pesados ​​en recursos que los hilos. Por lo general, solo desea crear un grupo de multiprocesamiento una vez al principio de un progtwig. Esta función de timeout crea un Pool cada vez que lo llamas. Esto fue necesario ya que necesitábamos pool.terminate() para terminar foo . Puede que haya una mejor manera, pero no lo he pensado.

Necesitas convertirlo en un hilo de demonio :

 it = ... it.daemon = True it.start() 

De lo contrario, se creará como un subproceso de usuario y el proceso no se detendrá hasta que todos los subprocesos de usuario hayan finalizado.

Tenga en cuenta que con su implementación, el subproceso continuará ejecutándose y consumiendo recursos incluso después de que haya agotado el tiempo de espera. El locking global de intérpretes de CPython podría agravar aún más el problema.