¿Hay alguna manera de matar un hilo?

Resuelto Sudden Def asked hace 15 años • 31 respuestas

¿Es posible terminar un hilo en ejecución sin configurar/verificar ningún indicador/semáforo/etc.?

Sudden Def avatar Nov 27 '08 21:11 Sudden Def
Aceptado

Generalmente es un mal patrón cerrar un hilo abruptamente, en Python y en cualquier idioma. Piense en los siguientes casos:

  • el hilo contiene un recurso crítico que debe cerrarse correctamente
  • el hilo ha creado varios otros hilos que también deben eliminarse.

La mejor manera de manejar esto, si puede permitírselo (si está administrando sus propios subprocesos), es tener un indicador exit_request que cada subproceso verifique en intervalos regulares para ver si es hora de salir.

Por ejemplo:

import threading

class StoppableThread(threading.Thread):
    """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""

    def __init__(self,  *args, **kwargs):
        super(StoppableThread, self).__init__(*args, **kwargs)
        self._stop_event = threading.Event()

    def stop(self):
        self._stop_event.set()

    def stopped(self):
        return self._stop_event.is_set()

En este código, debes llamar stop()al hilo cuando quieras que salga y esperar a que el hilo salga correctamente usando join(). El hilo debe comprobar la bandera de parada a intervalos regulares.

Sin embargo, hay casos en los que realmente es necesario cerrar un hilo. Un ejemplo es cuando está empaquetando una biblioteca externa que está ocupada para llamadas largas y desea interrumpirla.

El siguiente código permite (con algunas restricciones) generar una excepción en un hilo de Python:

def _async_raise(tid, exctype):
    '''Raises an exception in the threads with id tid'''
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
                                                     ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # "if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"
        ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class ThreadWithExc(threading.Thread):
    '''A thread class that supports raising an exception in the thread from
       another thread.
    '''
    def _get_my_tid(self):
        """determines this (self's) thread id

        CAREFUL: this function is executed in the context of the caller
        thread, to get the identity of the thread represented by this
        instance.
        """
        if not self.is_alive(): # Note: self.isAlive() on older version of Python
            raise threading.ThreadError("the thread is not active")

        # do we have it cached?
        if hasattr(self, "_thread_id"):
            return self._thread_id

        # no, look for it in the _active dict
        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid

        # TODO: in python 2.6, there's a simpler way to do: self.ident

        raise AssertionError("could not determine the thread's id")

    def raise_exc(self, exctype):
        """Raises the given exception type in the context of this thread.

        If the thread is busy in a system call (time.sleep(),
        socket.accept(), ...), the exception is simply ignored.

        If you are sure that your exception should terminate the thread,
        one way to ensure that it works is:

            t = ThreadWithExc( ... )
            ...
            t.raise_exc( SomeException )
            while t.isAlive():
                time.sleep( 0.1 )
                t.raise_exc( SomeException )

        If the exception is to be caught by the thread, you need a way to
        check that your thread has caught it.

        CAREFUL: this function is executed in the context of the
        caller thread, to raise an exception in the context of the
        thread represented by this instance.
        """
        _async_raise( self._get_my_tid(), exctype )

(Basado en Killable Threads de Tomer Filiba. La cita sobre el valor de retorno de PyThreadState_SetAsyncExcparece ser de una versión antigua de Python ).

Como se indica en la documentación, esto no es una solución mágica porque si el hilo está ocupado fuera del intérprete de Python, no detectará la interrupción.

Un buen patrón de uso de este código es hacer que el subproceso detecte una excepción específica y realice la limpieza. De esa manera, puedes interrumpir una tarea y aún así realizar una limpieza adecuada.

Philippe F avatar Nov 28 '2008 11:11 Philippe F

Una multiprocessing.Processlatap.terminate()

En los casos en los que quiero cerrar un hilo, pero no quiero usar banderas/bloqueos/señales/semáforos/eventos/lo que sea, promociono los hilos a procesos completos. Para el código que utiliza sólo unos pocos subprocesos, la sobrecarga no es tan mala.

Por ejemplo, esto resulta útil para terminar fácilmente los "hilos" auxiliares que ejecutan E/S de bloqueo.

La conversión es trivial: en el código relacionado, reemplace all threading.Threadwith multiprocessing.Processy all queue.Queuewith multiprocessing.Queuey agregue las llamadas requeridas p.terminate()a su proceso principal que quiere eliminar a su hijo.p

Consulte la documentación de Python paramultiprocessing .

Ejemplo:

import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
# Terminate the process
proc.terminate()  # sends a SIGTERM
cfi avatar Oct 13 '2011 09:10 cfi

No existe una API oficial para hacer eso, no.

Debe utilizar la API de la plataforma para eliminar el hilo, por ejemplo, pthread_kill o TerminateThread. Puede acceder a dicha API, por ejemplo, a través de pythonwin o mediante ctypes.

Tenga en cuenta que esto es inherentemente inseguro. Es probable que genere basura no recuperable (de las variables locales de los marcos de la pila que se convierten en basura) y puede provocar interbloqueos, si el subproceso que se elimina tiene el GIL en el momento en que se elimina.

Martin v. Löwis avatar Nov 27 '2008 15:11 Martin v. Löwis

Si está intentando finalizar todo el programa, puede configurar el hilo como un "demonio". verThread.daemon

schettino72 avatar Nov 03 '2009 14:11 schettino72