Reiniciando un hilo en Python

Estoy intentando crear un software de vuelo con hilos para un proyecto en Python 3.4, en el que necesito hilos para reiniciarse en caso de que se produzca un error de E / S durante la lectura de un sensor u otro accidente fortuito como ese. Por lo tanto, estoy trabajando en hacer un watchdog para verificar si los hilos han muerto y reiniciarlos.

Al principio intenté simplemente comprobar si el hilo ya no estaba vivo y reiniciarlo, lo que hizo esto:

>>> if not a_thread.isAlive(): ... a_thread.start() Traceback (most recent call last): File "", line 2, in  File "c:\Python34\lib\threading.py", line 847, in start raise RuntimeError("threads can only be started once") RuntimeError: threads can only be started once 

Este comportamiento tiene sentido desde el punto de vista de los threading y de Python en sí, pero hace que mi trabajo sea más difícil. Así que implementé una solución utilizando un diccionario para almacenar el hilo inicial y copiarlo en un nuevo objeto e iniciarlo cuando sea necesario. Desafortunadamente esto tampoco funciona. Aquí hay un ejemplo básico:

 import threading import logging import queue import time from copy import copy, deepcopy def a(): print("I'm thread a") def b(): print("I'm thread b") # Create thread objects thread_dict = { 'a': threading.Thread(target=a, name='a'), 'b': threading.Thread(target=b, name='b') } threads = [copy(t) for t in thread_dict.values()] for t in threads: t.start() for i in range(len(threads)): if not threads[i].isAlive(): temp = thread_dict[threads[i].name] threads[i] = deepcopy(temp) threads[i].start() thread(i).join(5) 

que devuelve:

 I'm thread a I'm thread b Traceback (most recent call last): File "main_test.py", line 25, in  threads[i] = deepcopy(temp) File "c:\Python34\lib\copy.py", line 182, in deepcopy y = _reconstruct(x, rv, 1, memo) ... (there's about 20 lines of traceback within copy) File "c:\Python34\lib\copyreg.py", line 88, in __newobj__ return cls.__new__(cls, *args) TypeError: object.__new__(_thread.lock) is not safe, use _thread.lock.__new__() 

Al parecer, los objetos de threading no son seguros de copiar … ¿Hay algún modo de reiniciar los subprocesos antes de recrear todo el objeto?

  1. No hay razón para dejar morir tus hilos.

Si realmente se están estrellando, todo el progtwig se bloqueará.

Si solo están generando excepciones, puedes capturar las excepciones.

Si están regresando normalmente, simplemente no puedes hacer eso.

Incluso puede ajustar de forma trivial una función de subproceso para reiniciarse en una excepción o devolver:

 def threadwrap(threadfunc): def wrapper(): while True: try: threadfunc() except BaseException as e: print('{!r}; restarting thread'.format(e)) else: print('exited normally, bad thread; restarting') return wrapper thread_dict = { 'a': threading.Thread(target=wrapper(a), name='a'), 'b': threading.Thread(target=wrapper(b), name='b') } 

Problema resuelto.


  1. No puedes reiniciar un hilo.

La mayoría de las plataformas no tienen forma de hacerlo.

Y conceptualmente, no tiene ningún sentido. Cuando un hilo termina, su stack está muerta; su padre está marcado o señalado; una vez que se une, sus recursos se destruyen (incluidos los recursos de nivel de kernel como su entrada en la tabla de procesos). La única forma de reiniciarlo sería crear un conjunto completamente nuevo de todo. Lo que ya puedes hacer creando un nuevo hilo.

Así que, sólo hazlo. Si realmente no quiere manejar las excepciones internamente, simplemente almacene los argumentos de construcción y utilícelos para iniciar un nuevo hilo.

Incluso puedes crear tu propia subclase que se cuelga de ellos para ti:

 class RestartableThread(threading.Thread): def __init__(self, *args, **kwargs): self._args, self._kwargs = args, kwargs super().__init__(*args, **kwargs) def clone(self): return RestartableThread(*args, **kwargs) 

Y ahora es fácil “copiar” el hilo (con la semántica que querías):

 if not a_thread.is_alive(): a_thread = a_thread.clone() 

  1. Sí, threading.Thread objetos de threading.Thread no son seguros para copiar

¿Qué esperarías que sucediera? En el mejor de los casos, obtendrías un envoltorio diferente alrededor del mismo objeto de subproceso a nivel del sistema operativo, por lo que engañarías a Python para que no notara que estás tratando de hacer las cosas ilegales, posiblemente inductoras de fallas de seguridad de las que intentaba impedirte. obra.

Como decían “higos”, deberías manejar las excepciones dentro del Hilo que intentar reiniciarlo. Consulte la documentación de la excepción aquí: https://docs.python.org/2/tutorial/errors.html

Es mucho más simple y python hacerlo.