¿Hay alguna manera de matar un hilo?

¿Es posible terminar un subproceso en ejecución sin configurar / verificar ninguna bandera / semáforo / etc?

Generalmente es un mal patrón matar un hilo de forma abrupta, 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 buena manera de manejar esto si puede permitírselo (si está administrando sus propios subprocesos) es tener un indicador de exit_request que compruebe cada subproceso en intervalos regulares para ver si es hora de que salga.

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): super(StoppableThread, self).__init__() 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, debe llamar a stop () en el hilo cuando desee 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 necesitas matar un hilo. Un ejemplo es cuando está envolviendo 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 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.isAlive(): 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 raiseExc(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.raiseExc( SomeException ) while t.isAlive(): time.sleep( 0.1 ) t.raiseExc( 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 excpetion in the context of the thread represented by this instance. """ _async_raise( self._get_my_tid(), exctype ) 

(Basado en los subprocesos Killable por Tomer Filiba. La cita sobre el valor de retorno de PyThreadState_SetAsyncExc parece ser de una versión anterior de Python ).

Como se señala en la documentación, esto no es una bala 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 capte una excepción específica y realice la limpieza. De esa manera, puede interrumpir una tarea y aun así tener la limpieza adecuada.

No hay una API oficial para hacer eso, no.

Debe usar 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 a través de ctypes.

Tenga en cuenta que esto es inherentemente inseguro. Es probable que genere basura no recolectable (de las variables locales de los marcos de la stack que se convierten en basura), y puede llevar a puntos muertos, si el hilo que se está matando tiene la GIL en el punto en que se mata.

Un proceso de multiprocessing.Process puede p.terminate()

En los casos en los que quiero eliminar un hilo, pero no quiero usar indicadores / lockings / señales / semáforos / eventos / lo que sea, promuevo los hilos a procesos completos. Para el código que utiliza solo algunos hilos, la sobrecarga no es tan mala.

Por ejemplo, esto es útil para terminar fácilmente los “hilos” auxiliares que ejecutan el locking de E / S

La conversión es trivial: en el código relacionado, reemplace todos los threading.Thread queue.Queue con multiprocessing.Process y all queue.Queue con multiprocessing.Queue y agregue las llamadas requeridas de p.terminate() a su proceso principal que quiere matar a su p secundario

Documento de Python

Si está intentando terminar todo el progtwig, puede establecer el hilo como un “demonio”. ver Thread.daemon

Esto se basa en thread2 – hilos killables (receta de Python)

Debe llamar a PyThreadState_SetasyncExc (), que solo está disponible a través de ctypes.

Esto solo se ha probado en Python 2.7.3, pero es probable que funcione con otras versiones recientes de la versión 2.x.

 import ctypes def terminate_thread(thread): """Terminates a python thread from another thread. :param thread: a threading.Thread instance """ if not thread.isAlive(): return exc = ctypes.py_object(SystemExit) res = ctypes.pythonapi.PyThreadState_SetAsyncExc( ctypes.c_long(thread.ident), exc) if res == 0: raise ValueError("nonexistent 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(thread.ident, None) raise SystemError("PyThreadState_SetAsyncExc failed") 

Nunca debes matar un hilo por la fuerza sin cooperar con él.

Matar un hilo elimina cualquier garantía de que los lockings de prueba / finalmente se configuren para que pueda dejar los lockings bloqueados, los archivos abiertos, etc.

La única vez que puede argumentar que matar a la fuerza los subprocesos es una buena idea es matar un progtwig rápidamente, pero nunca subprocesos únicos.

Como otros han mencionado, la norma es establecer una bandera de parada. Para algo ligero (sin subclase de Thread, sin variable global), una callback lambda es una opción. (Note los paréntesis en if stop() .)

 import threading import time def do_work(id, stop): print("I am thread", id) while True: print("I am thread {} doing something".format(id)) if stop(): print(" Exiting loop.") break print("Thread {}, signing off".format(id)) def main(): stop_threads = False workers = [] for id in range(0,3): tmp = threading.Thread(target=do_work, args=(id, lambda: stop_threads)) workers.append(tmp) tmp.start() time.sleep(3) print('main: done sleeping; time to stop the threads.') stop_threads = True for worker in workers: worker.join() print('Finis.') if __name__ == '__main__': main() 

Reemplazar print() con una función pr() que siempre se sys.stdout.flush() ) puede mejorar la precisión de la salida del shell.

(Solo probado en Windows / Eclipse / Python3.3)

En Python, simplemente no puedes matar un hilo directamente.

Si realmente NO necesita tener un subproceso (!), Lo que puede hacer, en lugar de usar el paquete de subprocesos , es usar el paquete de multiprocesamiento . Aquí, para matar un proceso, simplemente puede llamar al método:

 yourProcess.terminate() # kill the process! 

Python matará su proceso (en Unix a través de la señal SIGTERM, mientras que en Windows a través de la llamada TerminateProcess() ). ¡Presta atención a usarlo mientras usas una Cola o una Tubería! (Puede corromper los datos en la Cola / Tubería)

Tenga en cuenta que el multiprocessing.Event y el multiprocessing.Semaphore funcionan exactamente de la misma manera que el threading.Event y el threading.Semaphore respectivamente. De hecho, los primeros son clones de los últimos.

Si REALMENTE necesita utilizar un hilo, no hay forma de matarlo directamente. Sin embargo, lo que puede hacer es utilizar un “subproceso de daemon” . De hecho, en Python, un Thread se puede marcar como demonio :

 yourThread.daemon = True # set the Thread as a "daemon thread" 

El progtwig principal se cerrará cuando no queden hilos vivos que no sean de demonio. En otras palabras, cuando su subproceso principal (que es, por supuesto, un subproceso no daemon) finalice sus operaciones, el progtwig se cerrará incluso si todavía hay algunos subprocesos de daemon funcionando.

Tenga en cuenta que es necesario establecer un subproceso como daemon antes de llamar al método start() .

Por supuesto, puedes y debes usar el daemon incluso con multiprocessing . Aquí, cuando el proceso principal sale, intenta terminar todos sus procesos secundarios demoníacos.

Finalmente, tenga en cuenta que sys.exit() y os.kill() no son opciones.

Puede matar un hilo instalando el rastreo en el hilo que saldrá del hilo. Vea el enlace adjunto para una posible implementación.

Mata un hilo en Python

Es mejor si no mata un hilo. Una forma podría ser introducir un bloque de “prueba” en el ciclo del hilo y lanzar una excepción cuando desee detener el hilo (por ejemplo, un salto / retorno / … que detiene su para / while / …). He usado esto en mi aplicación y funciona …

Definitivamente es posible implementar un método Thread.stop como se muestra en el siguiente código de ejemplo:

 import sys import threading import time class StopThread(StopIteration): pass threading.SystemExit = SystemExit, StopThread class Thread2(threading.Thread): def stop(self): self.__stop = True def _bootstrap(self): if threading._trace_hook is not None: raise ValueError('Cannot run thread with tracing!') self.__stop = False sys.settrace(self.__trace) super()._bootstrap() def __trace(self, frame, event, arg): if self.__stop: raise StopThread() return self.__trace class Thread3(threading.Thread): def _bootstrap(self, stop_thread=False): def stop(): nonlocal stop_thread stop_thread = True self.stop = stop def tracer(*_): if stop_thread: raise StopThread() return tracer sys.settrace(tracer) super()._bootstrap() ############################################################################### def main(): test1 = Thread2(target=printer) test1.start() time.sleep(1) test1.stop() test1.join() test2 = Thread2(target=speed_test) test2.start() time.sleep(1) test2.stop() test2.join() test3 = Thread3(target=speed_test) test3.start() time.sleep(1) test3.stop() test3.join() def printer(): while True: print(time.time() % 1) time.sleep(0.1) def speed_test(count=0): try: while True: count += 1 except StopThread: print('Count =', count) if __name__ == '__main__': main() 

La clase Thread3 parece ejecutar el código aproximadamente un 33% más rápido que la clase Thread2 .

 from ctypes import * pthread = cdll.LoadLibrary("libpthread-2.15.so") pthread.pthread_cancel(c_ulong(t.ident)) 

t es tu objeto de Thread .

Lea la fuente de python ( Modules/threadmodule.c y Python/thread_pthread.h ). Puede ver que Thread.ident es un tipo pthread_t , por lo que puede hacer cualquier cosa que pthread pueda hacer en python use libpthread .

Una cosa que quiero agregar es que si lees la documentación oficial en threading lib Python , se recomienda evitar el uso de hilos “demoníacos”, cuando no quieres que los hilos terminen abruptamente, con la bandera que mencionó Paolo Rovelli.

De la documentación oficial:

Los hilos del demonio se detienen abruptamente al apagarse. Es posible que sus recursos (como archivos abiertos, transacciones de base de datos, etc.) no se publiquen correctamente. Si desea que sus hilos se detengan correctamente, conviértalos en no demoníacos y utilice un mecanismo de señalización adecuado, como un Evento.

Creo que la creación de hilos demoníacos depende de su aplicación, pero en general (y en mi opinión) es mejor evitar matarlos o hacerlos demoníacos. En el multiprocesamiento puede usar is_alive() para verificar el estado del proceso y “terminar” para finalizarlos (también evita los problemas de GIL). Pero puede encontrar más problemas, a veces, cuando ejecuta su código en Windows.

Y siempre recuerde que si tiene “hilos en vivo”, el intérprete de Python se ejecutará para esperarlos. (Debido a esto, la daemónica puede ayudarte si no importa, termina abruptamente).

La siguiente solución puede usarse para matar un hilo:

 kill_threads = False def doSomething(): global kill_threads while True: if kill_threads: thread.exit() ...... ...... thread.start_new_thread(doSomething, ()) 

Esto se puede usar incluso para terminar hilos, cuyo código está escrito en otro módulo, desde el hilo principal. Podemos declarar una variable global en ese módulo y usarla para terminar los subprocesos generados en ese módulo.

Usualmente uso esto para terminar todos los hilos en la salida del progtwig. Esta podría no ser la manera perfecta de terminar un / a hilo / s, pero podría ayudar.

Si está llamando explícitamente a time.sleep() como parte de su hilo (por ejemplo, encuestando a un servicio externo), una mejora en el método de Phillipe es usar el tiempo de espera en el método de wait() del event donde sea que sleep()

Por ejemplo:

 import threading class KillableThread(threading.Thread): def __init__(self, sleep_interval=1): super().__init__() self._kill = threading.Event() self._interval = sleep_interval def run(self): while True: print("Do Something") # If no kill signal is set, sleep for the interval, # If kill signal comes in while sleeping, immediately # wake up and handle is_killed = self._kill.wait(self._interval) if is_killed: break print("Killing Thread") def kill(self): self._kill.set() 

Luego correrlo

 t = KillableThread(sleep_interval=5) t.start() # Every 5 seconds it prints: #: Do Something t.kill() #: Killing Thread 

La ventaja de usar wait() lugar de sleep() ing y verificar regularmente el evento es que puede progtwigr en intervalos más prolongados de sleep, el hilo se detiene casi inmediatamente (cuando de lo contrario estaría en sleep() ing) y en mi opinión , el código para manejar la salida es significativamente más simple.

Llego tarde a este juego, pero he estado luchando con una pregunta similar y lo siguiente parece resolver el problema a la perfección para mí Y me permite realizar una comprobación y limpieza del estado del subproceso básico cuando el subproceso del daemonized sale:

 import threading import time import atexit def do_work(): i = 0 @atexit.register def goodbye(): print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" % (i, threading.currentThread().ident)) while True: print i i += 1 time.sleep(1) t = threading.Thread(target=do_work) t.daemon = True t.start() def after_timeout(): print "KILL MAIN THREAD: %s" % threading.currentThread().ident raise SystemExit threading.Timer(2, after_timeout).start() 

Rendimientos:

 0 1 KILL MAIN THREAD: 140013208254208 'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568] 

Esta es una mala respuesta, ver los comentarios.

Aquí está cómo hacerlo:

 from threading import * ... for thread in enumerate(): if thread.isAlive(): try: thread._Thread__stop() except: print(str(thread.getName()) + ' could not be terminated')) 

Dale unos segundos y luego tu hilo debería detenerse. Compruebe también el método thread._Thread__delete() .

Recomendaría un método thread.quit() por conveniencia. Por ejemplo, si tiene un socket en su hilo, recomiendo crear un método quit() en su clase de socket-handle, terminar el socket, luego ejecutar un thread._Thread__stop() dentro de su quit() .

Hay una biblioteca construida para este propósito, stopit . Aunque algunas de las precauciones enumeradas en este documento siguen vigentes, al menos esta biblioteca presenta una técnica regular y repetible para lograr el objective establecido.

Esto parece funcionar con pywin32 en windows 7

 my_thread = threading.Thread() my_thread.start() my_thread._Thread__stop() 

Inicie el subproceso con setDaemon (True).

 def bootstrap(_filename): mb = ModelBootstrap(filename=_filename) # Has many Daemon threads. All get stopped automatically when main thread is stopped. t = threading.Thread(target=bootstrap,args=('models.conf',)) t.setDaemon(False) while True: t.start() time.sleep(10) # I am just allowing the sub-thread to run for 10 sec. You can listen on an event to stop execution. print('Thread stopped') break 

Si bien es bastante antiguo, esta podría ser una solución útil para algunos:

Un pequeño módulo que amplía la funcionalidad del módulo de subprocesos: permite que un subproceso genere excepciones en el contexto de otro subproceso. Al elevar SystemExit , finalmente puedes matar los hilos de python.

 import threading import ctypes def _async_raise(tid, excobj): res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(excobj)) if res == 0: raise ValueError("nonexistent 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(tid, 0) raise SystemError("PyThreadState_SetAsyncExc failed") class Thread(threading.Thread): def raise_exc(self, excobj): assert self.isAlive(), "thread must be started" for tid, tobj in threading._active.items(): if tobj is self: _async_raise(tid, excobj) return # the thread was alive when we entered the loop, but was not found # in the dict, hence it must have been already terminated. should we raise # an exception here? silently ignore? def terminate(self): # must raise the SystemExit type, instead of a SystemExit() instance # due to a bug in PyThreadState_SetAsyncExc self.raise_exc(SystemExit) 

Por lo tanto, permite que un “subproceso genere excepciones en el contexto de otro subproceso” y de esta manera, el subproceso terminado puede manejar la terminación sin verificar regularmente un indicador de cancelación.

Sin embargo, según su fuente original , hay algunos problemas con este código.

  • La excepción se generará solo cuando se ejecute el bytecode de Python. Si su hilo llama a una función de locking nativa / incorporada, la excepción se generará solo cuando la ejecución vuelva al código de Python.
    • También hay un problema si la función incorporada llama a PyErr_Clear () internamente, lo que cancelaría efectivamente su excepción pendiente. Puedes intentar levantarlo de nuevo.
  • Sólo los tipos de excepción se pueden elevar de forma segura. Es probable que las instancias de excepción causen un comportamiento inesperado y, por lo tanto, estén restringidas.
  • Pedí que se expusiera esta función en el módulo de subproceso integrado, pero como ctypes se ha convertido en una biblioteca estándar (a partir de 2.5), y esto
    no es probable que la característica sea agnóstica en la implementación, se puede mantener
    no expuesto

Pieter Hintjens, uno de los fundadores del proyecto ØMQ , dice que usar ØMQ y evitar primitivas de sincronización como lockings, exclusión mutua, eventos, etc. es la forma más segura y segura de escribir progtwigs de múltiples hilos:

http://zguide.zeromq.org/py:all#Multithreading-with-ZeroMQ

Esto incluye decirle a un hilo hijo, que debe cancelar su trabajo. Esto se haría equipando el hilo con un zócalo ØMQ y sondeando en ese zócalo un mensaje que indique que debería cancelarse.

El enlace también proporciona un ejemplo de código de Python con subprocesos múltiples con ØMQ.

Puede ejecutar su comando en un proceso y luego eliminarlo usando la ID de proceso. Necesitaba sincronizar entre dos hilos, uno de los cuales no vuelve por sí mismo.

 processIds = [] def executeRecord(command): print(command) process = subprocess.Popen(command, stdout=subprocess.PIPE) processIds.append(process.pid) print(processIds[0]) #Command that doesn't return by itself process.stdout.read().decode("utf-8") return; def recordThread(command, timeOut): thread = Thread(target=executeRecord, args=(command,)) thread.start() thread.join(timeOut) os.kill(processIds.pop(), signal.SIGINT) return; 

Si realmente necesita la capacidad de eliminar una subtarea, use una implementación alternativa. multiprocessing y gevent soportan la eliminación indiscriminada de un “hilo”.

El enhebrado de Python no admite la cancelación. Ni lo intentes. Es muy probable que su código se bloquee, corrompa o pierda memoria, o que tenga otros efectos “interesantes” difíciles de depurar no intencionados que ocurren de manera poco frecuente y no determinista.