¿Hay alguna forma de matar un hilo?
-
11-07-2019 - |
Pregunta
¿Es posible terminar un subproceso en ejecución sin configurar / verificar ningún indicador / semáforo / etc.?
Solución
Generalmente es un mal patrón matar 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 ser eliminados.
La mejor manera de manejar esto si puede permitírselo (si está administrando sus propios subprocesos) es tener un indicador de salida_request que cada subproceso verifique en el intervalo regular 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 quiera que salga y esperar a que el hilo salga correctamente usando join ()
. El hilo debe verificar la bandera de detención a intervalos regulares.
Sin embargo, hay casos en los que realmente necesitas matar un hilo. Un ejemplo es cuando está ajustando una biblioteca externa que está ocupada por 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 Subprocesos eliminables de Tomer Filiba. La cita sobre el valor de retorno de
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 hilo detecte una excepción específica y realice la limpieza. De esa manera, puede interrumpir una tarea y aún tener la limpieza adecuada.
Otros consejos
No hay una API oficial para hacer eso, no.
Debe usar la API de plataforma para eliminar el hilo, p. ej. pthread_kill o TerminateThread. Puede acceder a dicha API, p. a través de pythonwin, o a través de ctypes.
Tenga en cuenta que esto es inherentemente inseguro. Probablemente conducirá a basura incobrable (de variables locales de los marcos de la pila que se convierten en basura), y puede conducir a puntos muertos, si el hilo que se está matando tiene el GIL en el momento en que se mata.
Un multiprocessing.Process
puede p.terminate()
En los casos en que quiero matar un hilo, pero no quiero usar banderas / bloqueos / señales / semáforos / eventos / lo que sea, promuevo los hilos a procesos completos. Para el código que utiliza solo unos pocos hilos, la sobrecarga no es tan mala.
Por ejemplo. esto es útil para terminar fácilmente los hilos auxiliares '' que ejecutan el bloqueo de E / S
La conversión es trivial: en el código relacionado, reemplace todos los threading.Thread
con multiprocessing.Process
y todos los queue.Queue
con multiprocessing.Queue
y agregue las llamadas requeridas de p.terminate ()
a su proceso padre que quiere matar a su hijo p
Si está intentando terminar todo el programa, puede configurar el hilo como un "daemon". ver Thread.daemon
Esto se basa en thread2 - hilos que se pueden matar (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 2.x recientes.
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 a la fuerza un hilo sin cooperar con él.
Eliminar un hilo elimina cualquier garantía que intente / finalmente bloquee la configuración, por lo que puede dejar bloqueados, archivos abiertos, etc.
La única vez que puede argumentar que matar hilos a la fuerza es una buena idea es matar un programa rápidamente, pero nunca hilos individuales.
Como otros han mencionado, la norma es establecer una bandera de detención. Para algo ligero (sin subclases de Thread, sin variable global), una devolución de llamada lambda es una opción. (Tenga en cuenta 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 enjuaga ( 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 (!), qué puede hacer, en lugar de utilizar el subprocesamiento paquete , es utilizar 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 para usarlo mientras usas una cola o una tubería! (puede dañar los datos en la Cola / Canalización)
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 necesitas usar un hilo, no hay forma de matarlo directamente. Sin embargo, lo que puede hacer es utilizar un " hilo de demonio " . De hecho, en Python, un Thread se puede marcar como daemon :
yourThread.daemon = True # set the Thread as a "daemon thread"
El programa principal se cerrará cuando no queden hilos no daemon vivos. En otras palabras, cuando su hilo principal (que es, por supuesto, un hilo que no sea de demonio) termine sus operaciones, el programa se cerrará incluso si todavía hay algunos hilos de demonio funcionando.
Tenga en cuenta que es necesario establecer un subproceso como daemon
antes de que se llame al método start ()
!
Por supuesto que puede y debe usar daemon
incluso con multiprocesamiento
. Aquí, cuando finaliza el proceso principal, intenta finalizar 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.
Es mejor si no matas un hilo. Una forma podría ser introducir un " intentar " bloquee en el ciclo del hilo y arroje una excepción cuando desee detener el hilo (por ejemplo, un descanso / retorno / ... que detiene su for / 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 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 su objeto Thread
.
Lea la fuente de Python ( Modules / threadmodule.c
y Python / thread_pthread.h
) puede ver que Thread.ident
es un pthread_t
, para que pueda hacer cualquier cosa que pthread
pueda hacer en python, use libpthread
.
La siguiente solución alternativa se puede usar 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 puede usarse 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 hilos generados en ese módulo.
Usualmente uso esto para terminar todos los hilos en la salida del programa. Esta podría no ser la manera perfecta de terminar hilos / s pero podría ayudar.
Una cosa que quiero agregar es que si lee la documentación oficial en threading lib Python , se recomienda evitar el uso de " demonic " subprocesos, cuando no desea que los subprocesos terminen abruptamente, con la bandera que Paolo Rovelli mencionó .
De la documentación oficial:
Los hilos del demonio se detienen abruptamente al apagarse. Es posible que sus recursos (como archivos abiertos, transacciones de bases de datos, etc.) no se liberen correctamente. Si desea que sus hilos se detengan con gracia, hágalos no demoníacos y utilice un mecanismo de señalización adecuado, como un Evento.
Creo que crear 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 terminarlos (también evitas problemas GIL). Pero puede encontrar más problemas, a veces, cuando ejecuta su código en Windows.
Y recuerda siempre que si tienes "hilos en vivo", el intérprete de Python se ejecutará para esperarlos. (Debido a esto daemonic puede ayudarte si no importa abruptamente termina).
Si está llamando explícitamente a time.sleep ()
como parte de su hilo (digamos sondear algún servicio externo), una mejora con respecto al método de Phillipe es usar el tiempo de espera en el evento
método wait ()
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 para ejecutarlo
t = KillableThread(sleep_interval=5)
t.start()
# Every 5 seconds it prints:
#: Do Something
t.kill()
#: Killing Thread
La ventaja de usar wait ()
en lugar de sleep ()
ing y verificar regularmente el evento es que puede programar en intervalos de sueño más largos, el hilo se detiene casi de inmediato (cuando de otro modo estaría sleep ()
ing) y, en mi opinión, el código para manejar la salida es significativamente más simple.
Llegué tarde a este juego, pero he estado luchando con una pregunta similar y aparece lo siguiente para resolver el problema perfectamente para mí Y me permite hacer una comprobación básica del estado del hilo y limpiarlo cuando sale el subproceso demonizado:
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]
Hay una biblioteca creada para este propósito, stopit . Aunque todavía se aplican algunas de las mismas precauciones enumeradas en este documento, al menos esta biblioteca presenta una técnica regular y repetible para lograr el objetivo establecido.
Esto parece funcionar con pywin32 en Windows 7
my_thread = threading.Thread()
my_thread.start()
my_thread._Thread__stop()
Esta es una mala respuesta, vea los comentarios
Aquí se explica 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'))
Déle unos segundos, entonces su hilo debería detenerse. Compruebe también el método thread._Thread__delete ()
.
Recomiendo un método thread.quit ()
por conveniencia. Por ejemplo, si tiene un socket en su hilo, recomendaría crear un método quit ()
en su clase de socket-handle, terminar el socket y luego ejecutar un hilo ._Thread__stop ()
dentro de su quit ()
.
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, esto puede ser útil solución para algunos:
Un pequeño módulo que extiende la funcionalidad del módulo de subprocesos - permite que un hilo genere excepciones en el contexto de otro hilo. Al subir
SystemExit
, finalmente puede 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 hilo '' genere excepciones en el contexto de otro hilo '' y de esta manera, el subproceso terminado puede manejar la terminación sin verificar regularmente un indicador de aborto.
Sin embargo, de acuerdo con su fuente original , hay algunos problemas con este código.
- La excepción se generará solo cuando se ejecute el código de bytes de Python. Si su hilo llama a una función de bloqueo nativa / incorporada, el la excepción se generará solo cuando la ejecución regrese a la pitón código.
- También hay un problema si la función integrada llama internamente a PyErr_Clear (), lo que efectivamente cancelaría su excepción pendiente. Puedes intentar subirlo de nuevo.
- Solo los tipos de excepción se pueden generar de forma segura. Es probable que las instancias de excepción provoquen un comportamiento inesperado y, por lo tanto, estén restringidas.
- Por ejemplo: t1.raise_exc (TypeError) y no t1.raise_exc (TypeError (" blah ")).
- En mi humilde opinión es un error, y lo informé como uno. Para obtener más información, http://mail.python.org/ pipermail / python-dev / 2006-agosto / 068158.html
- Pedí exponer esta función en el módulo de subprocesos incorporado, pero dado que ctypes se ha convertido en una biblioteca estándar (a partir de 2.5), y esto
no es probable que la función sea independiente de la implementación, se puede mantener
sin exponer.
Pieter Hintjens - uno de los fundadores del ØMQ -project - dice, usar ØMQ y evitar las primitivas de sincronización como bloqueos, mutexes, eventos, etc., es la forma más segura y segura de escribir programas multiproceso:
http://zguide.zeromq.org/py:all#Multithreading -with-ZeroMQ
Esto incluye decirle a un hilo secundario 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 python multiproceso con ØMQ.
Puede ejecutar su comando en un proceso y luego matarlo usando la identificación del proceso. Necesitaba sincronizar entre dos hilos, uno de los cuales no regresa solo.
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. multiprocesamiento
y gevent
ambos admiten matar indiscriminadamente un " thread " ;.
El subproceso de Python no admite la cancelación. Ni lo intentes. Es muy probable que su código se bloquee, corrompa o pierda memoria, o tenga otros "interesantes" involuntarios efectos difíciles de depurar que ocurren raramente y de forma no determinante.