¿Es posible terminar un subproceso en ejecución sin establecer / verificar ningún indicador / semáforo / etc.?
fuente
¿Es posible terminar un subproceso en ejecución sin establecer / verificar ningún indicador / semáforo / etc.?
En general, es un mal patrón matar un hilo abruptamente, en Python y en cualquier idioma. Piense en los siguientes casos:
La buena manera de manejar esto si puede permitírselo (si está administrando sus propios subprocesos) es tener un indicador exit_request que cada subproceso verifique a 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, *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, debe llamar stop()
al hilo cuando desee 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 hilos eliminables de Tomer Filiba. La cita sobre el valor de retorno de PyThreadState_SetAsyncExc
parece ser de una versión antigua de Python ).
Como se señaló 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.
SO_REUSEADDR
opción de socket para evitarAddress already in use
errores.None
en lugar de0
para elres != 1
caso, y tuve que llamarctypes.c_long(tid)
y pasar a que ningún ctypes función más que el tres veces al día directamente.No hay 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. Probablemente conducirá a la basura incobrable (de las 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.
fuente
Una
multiprocessing.Process
latap.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 usa solo unos pocos hilos, la sobrecarga no es tan mala.
Por ejemplo, esto es útil para terminar fácilmente "hilos" auxiliares que ejecutan el bloqueo de E / S
La conversión es trivial: en el código relacionado, reemplace todos
threading.Thread
conmultiprocessing.Process
y todosqueue.Queue
conmultiprocessing.Queue
y agregue las llamadas requeridasp.terminate()
a su proceso padre que quiere matar a su hijop
Consulte la documentación de Python para
multiprocessing
.fuente
multiprocessing
es bueno, pero tenga en cuenta que los argumentos se enredan en el nuevo proceso. Entonces, si uno de los argumentos es algo no seleccionable (como alogging.log
), podría no ser una buena idea usarmultiprocessing
.multiprocessing
Los argumentos se encuadran en el nuevo proceso en Windows, pero Linux utiliza la bifurcación para copiarlos (Python 3.7, sin saber qué otras versiones). Por lo tanto, terminará con un código que funciona en Linux pero genera errores de encurtido en Windows.multiprocessing
con el registro es un negocio complicado. Necesito usarQueueHandler
(ver este tutorial ). Lo aprendí de la manera difícil.Si está intentando terminar todo el programa, puede configurar el hilo como un "demonio". ver Thread.daemon
fuente
Como otros han mencionado, la norma es establecer una bandera de alto. 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()
.)Reemplazar
print()
con unapr()
función que siempre se vacía (sys.stdout.flush()
) puede mejorar la precisión de la salida del shell.(Solo probado en Windows / Eclipse / Python3.3)
fuente
pr()
función?Esto se basa en thread2 - hilos eliminables (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.
fuente
KeyboardInterrupt
para que tengan la oportunidad de limpiar. Si TODAVÍA cuelgan después de eso, entoncesSystemExit
es apropiado, o simplemente eliminan el proceso desde una terminal.pthread_cleanup_push()/_pop()
, sería mucho trabajo implementar correctamente y ralentizaría notablemente al intérprete.Nunca debe matar a la fuerza un hilo sin cooperar con él.
Matar un hilo elimina cualquier garantía que intente / finalmente bloquee la configuración para que pueda dejar bloqueados, archivos abiertos, etc.
El único momento en que puede argumentar que matar hilos por la fuerza es una buena idea es matar un programa rápidamente, pero nunca hilos individuales.
fuente
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:
Python matará su proceso (en Unix a través de la señal SIGTERM, mientras que en Windows a través de la
TerminateProcess()
llamada). ¡Presta atención para 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 elmultiprocessing.Semaphore
trabajo exactamente de la misma manera que elthreading.Event
y elthreading.Semaphore
respectivamente. De hecho, los primeros son clones de los últimos.Si REALMENTE necesita usar un hilo, no hay forma de matarlo directamente. Sin embargo, lo que puede hacer es usar un "hilo de demonio" . De hecho, en Python, un Thread se puede marcar como daemon :
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 es 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 destart()
llamar al método.Por supuesto que puede y debe usar
daemon
incluso conmultiprocessing
. Aquí, cuando el proceso principal sale, intenta finalizar todos sus procesos secundarios demoníacos.Finalmente, tenga en cuenta que
sys.exit()
yos.kill()
no son opciones.fuente
Puede matar un subproceso instalando el rastreo en el subproceso que saldrá del subproceso. Vea el enlace adjunto para una posible implementación.
Mata un hilo en Python
fuente
Si está llamando explícitamente
time.sleep()
como parte de su hilo (por ejemplo, encuestando algún servicio externo), una mejora con respecto al método de Phillipe es utilizar el tiempo de espera en elevent
'swait()
método de donde quiera que ustedsleep()
Por ejemplo:
Entonces para correrlo
La ventaja de usar en
wait()
lugar desleep()
comprobar el evento regularmente es que puede programar en intervalos de sueño más largos, el subproceso se detiene casi de inmediato (cuando de otro modo estaríasleep()
ing) y, en mi opinión, el código para manejar la salida es significativamente más simple .fuente
Es mejor si no matas un hilo. Una forma podría ser introducir un bloque "try" en el ciclo del hilo y lanzar 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 ...
fuente
Definitivamente es posible implementar un
Thread.stop
método como se muestra en el siguiente código de ejemplo:La
Thread3
clase parece ejecutar código aproximadamente un 33% más rápido que laThread2
clase.fuente
self.__stop
conjunto en el hilo. Tenga en cuenta que, como la mayoría de las otras soluciones aquí, en realidad no interrumpirá una llamada de bloqueo, ya que la función de rastreo solo se llama cuando se ingresa un nuevo alcance local. También vale la pena señalar quesys.settrace
realmente está destinado a implementar depuradores, perfiles, etc. y como tal se considera un detalle de implementación de CPython, y no se garantiza que exista en otras implementaciones de Python.Thread2
clase es que ejecuta código aproximadamente diez veces más lento. Algunas personas aún pueden encontrar esto aceptable.t es su
Thread
objeto.Lea la fuente de Python (
Modules/threadmodule.c
yPython/thread_pthread.h
) puede ver queThread.ident
es unpthread_t
tipo, por lo que puede hacer cualquier cosa quepthread
pueda hacer con el uso de Pythonlibpthread
.fuente
La siguiente solución alternativa se puede usar para matar un hilo:
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 a la salida del programa. Puede que esta no sea la manera perfecta de terminar hilos / s pero podría ayudar.
fuente
Llegué tarde a este juego, pero he estado luchando con una pregunta similar y lo siguiente parece resolver el problema perfectamente para mí Y me permite hacer una comprobación y limpieza básica del estado del hilo cuando sale el subproceso demonizado:
Rendimientos:
fuente
SystemExit
elafter_timeout
hilo haría algo al hilo principal (que simplemente está esperando que el primero salga en este ejemplo)?SystemExit
solo tiene dos propiedades especiales: no produce un rastreo (cuando un subproceso sale lanzando uno), y si el subproceso principal sale lanzando uno, establece el estado de salida (mientras espera otros subprocesos que no sean demonios salir).Una cosa que quiero agregar es que si lees la documentación oficial al enhebrar lib Python , se recomienda evitar el uso de hilos "demoníacos", cuando no quieras que los hilos terminen abruptamente, con la bandera que mencionó Paolo Rovelli .
De la documentación oficial:
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 "finalizar" 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 recuerde siempre que si tiene "hilos en vivo", el intérprete de Python se ejecutará para esperarlos. (Debido a esto daemonic puede ayudarte si no importa termina abruptamente).
fuente
Hay una biblioteca construida para este propósito, detente . 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.
fuente
Si bien es bastante antiguo, esta podría ser una solución útil para algunos:
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.
fuente
Esto parece funcionar con pywin32 en Windows 7
fuente
Pieter Hintjens - uno de los fundadores de ØMQ , dice que 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 de subprocesos múltiples:
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 multihilo con ØMQ.
fuente
Asumiendo que desea tener múltiples subprocesos de la misma función, esta es, en mi humilde opinión, la implementación más fácil para detener uno por id:
Lo bueno es que puedes tener múltiples funciones iguales y diferentes, y detenerlas todas
functionname.stop
Si desea tener solo un hilo de la función, entonces no necesita recordar la identificación. Solo detente, si
doit.stop
> 0.fuente
Solo para construir sobre la idea de @ SCB (que era exactamente lo que necesitaba) para crear una subclase de KillableThread con una función personalizada:
Naturalmente, como con @SBC, el hilo no espera para ejecutar un nuevo ciclo para detenerse. En este ejemplo, verá el mensaje "Killing Thread" impreso justo después de "About to kill thread" en lugar de esperar 4 segundos más para que se complete el hilo (ya que hemos dormido durante 6 segundos).
El segundo argumento en el constructor KillableThread es su función personalizada (print_msg aquí). El argumento Args son los argumentos que se utilizarán al llamar a la función (("hola mundo")) aquí.
fuente
Como se menciona en la respuesta de @ Kozyarchuk , la instalación de trazas funciona. Como esta respuesta no contenía ningún código, aquí hay un ejemplo de trabajo listo para usar:
Se detiene después de haber impreso
1
y2
.3
No está impreso.fuente
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 por sí mismo.
fuente
Inicie el subproceso con setDaemon (True).
fuente
Aquí se explica cómo hacerlo:
Déle unos segundos, entonces su hilo debería detenerse. Compruebe también el
thread._Thread__delete()
método.Recomendaría un
thread.quit()
método por conveniencia. Por ejemplo, si tiene un socket en su hilo, recomendaría crear unquit()
método en su clase socket-handle, terminar el socket y luego ejecutar unthread._Thread__stop()
interior de suquit()
.fuente
_Thread__stop()
simplemente marca un hilo como detenido , ¡en realidad no lo detiene! Nunca hagas esto. Tiene una lectura .Si realmente necesita la capacidad de eliminar una subtarea, use una implementación alternativa.
multiprocessing
ygevent
ambos apoyan matar indiscriminadamente un "hilo".El subproceso de Python no admite la cancelación. Ni lo intentes. Es muy probable que su código tenga un punto muerto, corrompa o pierda memoria, o tenga otros efectos "interesantes" difíciles de depurar no intencionados que ocurren raramente y de manera no determinante.
fuente