¿Hay alguna forma de matar un hilo?

Respuestas:

672

En general, 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 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_SetAsyncExcparece 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.

Philippe F
fuente
78
@ Bluebird75: Además, no estoy seguro de tener el argumento de que los subprocesos no deben eliminarse abruptamente "porque el subproceso podría contener un recurso crítico que debe cerrarse correctamente": esto también es cierto desde un programa principal y programas principales puede ser asesinado abruptamente por el usuario (Ctrl-C en Unix, por ejemplo), en cuyo caso intentan manejar esta posibilidad de la mejor manera posible. Por lo tanto, no veo qué tienen de especial los hilos y por qué no deberían recibir el mismo tratamiento que los programas principales (es decir, que pueden ser eliminados abruptamente). :) ¿Podrías dar más detalles sobre esto?
Eric O Lebigot
18
@EOL: Por otro lado, si todos los recursos que posee el hilo son recursos locales (archivos abiertos, sockets), Linux es razonablemente bueno en la limpieza del proceso y esto no se pierde. Sin embargo, tuve casos en los que creé un servidor usando un socket, y si hago una interrupción brutal con Ctrl-C, ya no puedo iniciar el programa porque no puede vincular el socket. Necesito esperar 5 minutos. La solución adecuada era atrapar Ctrl-C y hacer una desconexión limpia del zócalo.
Philippe F
10
@ Bluebird75: por cierto. Puede usar la SO_REUSEADDRopción de socket para evitar Address already in useerrores.
Messa
12
Nota sobre esta respuesta: al menos para mí (py2.6), tuve que pasar Noneen lugar de 0para el res != 1caso, y tuve que llamar ctypes.c_long(tid)y pasar a que ningún ctypes función más que el tres veces al día directamente.
Walt W
21
Vale la pena mencionar que _stop ya está ocupado en la biblioteca de subprocesos de Python 3. Como tal, tal vez use una variable diferente, de lo contrario obtendrá un error.
murió en
113

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.

Martin v. Löwis
fuente
26
Se va a llevar a callejones sin salida si el hilo en cuestión tiene el GIL.
Matthias Urlichs
96

Una multiprocessing.Processlatap.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.Threadcon multiprocessing.Processy todos queue.Queuecon multiprocessing.Queuey agregue las llamadas requeridas p.terminate()a su proceso padre que quiere matar a su hijop

Consulte la documentación de Python paramultiprocessing .

cfi
fuente
Gracias. Reemplacé cola.Queue con multiprocesamiento.JoinableQueue y seguí esta respuesta: stackoverflow.com/a/11984760/911207
David Braun
Muchas páginas sobre este tema. Esta parece ser la solución obvia para muchos, creo
geoteoría
66
multiprocessinges 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 a logging.log), podría no ser una buena idea usar multiprocessing.
Lyager
1
multiprocessingLos 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.
nyanpasu64
multiprocessingcon el registro es un negocio complicado. Necesito usar QueueHandler(ver este tutorial ). Lo aprendí de la manera difícil.
Fanchen Bao
74

Si está intentando terminar todo el programa, puede configurar el hilo como un "demonio". ver Thread.daemon

schettino72
fuente
Esto no tiene ningún sentido. La documentación establece claramente que "esto debe establecerse antes de que se llame a start (), de lo contrario se genera RuntimeError". Por lo tanto, si quiero matar un hilo que originalmente no era un demonio, ¿cómo puedo usar esto?
Raffi Khatchadourian
27
Raffi Creo que está sugiriendo que lo configures con anticipación, sabiendo que cuando tu hilo principal salga, también querrás que salgan los hilos del demonio.
fantabolous
1
No estoy seguro de por qué esta no es la respuesta aceptada
Eric
¿No es establecer un hilo como demonio algo que harías en caso de que quieras que el hilo siga ejecutándose incluso si tu programa principal se apaga?
Michele Piccolini
Usted señor, es mi héroe del día. Exactamente lo que estaba buscando y sin complicaciones para agregar.
Blizz
42

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().)

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 pr()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)

Jon Coombs
fuente
1
Verificado en Linux / Python 2.7, funciona de maravilla. Esta debería ser la respuesta oficial, es mucho más simple.
Paul Kenjora
1
Verificado en Linux Ubuntu Server 17.10 / Python 3.6.3 y funciona.
Marcos
1
También verificado en 2.7. ¡Qué buena respuesta!
silgon
¿Qué es la pr()función?
Alper
35

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.

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")
Johan Dahlin
fuente
Estoy usando algo como esto para darles un hilo a mis hilos KeyboardInterruptpara que tengan la oportunidad de limpiar. Si TODAVÍA cuelgan después de eso, entonces SystemExites apropiado, o simplemente eliminan el proceso desde una terminal.
drevicko
Esto funciona si el hilo se está ejecutando actualmente. No funciona si el hilo está en una llamada al sistema; la excepción será ignorada en silencio.
Matthias Urlichs
@MatthiasUrlichs alguna idea de cómo detectar cuál es el estado de ejecución del subproceso, para poder imprimir una advertencia o reintentar?
Johan Dahlin
1
@JohanDahlin Puedes esperar un poco (que, si quieres volver a intentarlo, debes hacerlo de todos modos) y luego hacer la prueba isAlive (). En cualquier caso, si bien esto funcionaría, tampoco garantizaría que no deje referencias colgantes. Si bien es posible, en teoría, hacer que la ejecución de hilos sea segura en CPython, mediante un uso juicioso pthread_cleanup_push()/_pop(), sería mucho trabajo implementar correctamente y ralentizaría notablemente al intérprete.
Matthias Urlichs
32

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.

Lasse V. Karlsen
fuente
11
¿Por qué es tan difícil decir un hilo? Por favor, mátate cuando termines tu ciclo actual ... No lo entiendo.
Mehdi
2
No hay ningún mecanismo integrado en la CPU para identificar un "bucle" como tal, lo mejor que puede esperar es usar algún tipo de señal de que el código que está actualmente dentro del bucle verificará una vez que salga. La forma correcta de manejar la sincronización de subprocesos es por medios cooperativos, la suspensión, reanudación y eliminación de subprocesos son funciones que están destinadas a los depuradores y al sistema operativo, no al código de la aplicación.
Lasse V. Karlsen
2
@Mehdi: si (personalmente) estoy escribiendo el código en el hilo, sí, estoy de acuerdo contigo. Pero hay casos en los que estoy ejecutando bibliotecas de terceros, y no tengo acceso al bucle de ejecución de ese código. Ese es un caso de uso para la función solicitada.
Dan H
@DanH Es aún peor con el código de terceros, ya que no tienes idea de qué daño puede causar. Si su biblioteca de terceros no es lo suficientemente robusta como para que deba ser eliminada, entonces debe hacer uno de estos: (1) pedirle al autor que solucione el problema, (2) usar otra cosa. Si realmente no tiene otra opción, poner ese código en un proceso distinto debería ser más seguro ya que algunos recursos se comparten solo en un solo proceso.
Phil1970
25

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 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.Eventy el multiprocessing.Semaphoretrabajo exactamente de la misma manera que el threading.Eventy 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 :

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 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 daemonantes de start()llamar al método.

Por supuesto que puede y debe usar daemonincluso con multiprocessing. Aquí, cuando el proceso principal sale, intenta finalizar todos sus procesos secundarios demoníacos.

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

Paolo Rovelli
fuente
14

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

Kozyarchuk
fuente
Ya lo he visto. Esta solución se basa en la comprobación de la bandera de self.killed
Def. Repentina
1
Una de las pocas respuestas aquí que realmente FUNCIONA
Ponkadoodle
55
Dos problemas con esta solución: (a) instalar un rastreador con sys.settrace () hará que su hilo se ejecute más lentamente. Tanto como 10 veces más lento si está vinculado al cálculo. (b) no afectará su hilo mientras está en una llamada al sistema.
Matthias Urlichs
10

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 el event'swait() método de donde quiera que ustedsleep()

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()

Entonces para correrlo

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

La ventaja de usar en wait()lugar de sleep()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ía sleep()ing) y, en mi opinión, el código para manejar la salida es significativamente más simple .

SCB
fuente
3
¿Por qué esta publicación fue rechazada? ¿Qué pasa con esta publicación? Se ve exactamente como lo que necesito ...
JDOaktown
9

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 ...

Giancarlo
fuente
8

Definitivamente es posible implementar un Thread.stopmétodo 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 Thread3clase parece ejecutar código aproximadamente un 33% más rápido que la Thread2clase.

Noctis Skytower
fuente
3
Esta es una forma inteligente de inyectar cheques para el self.__stopconjunto 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 que sys.settracerealmente 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.
dano
3
@dano: Uno de los mayores problemas con la Thread2clase es que ejecuta código aproximadamente diez veces más lento. Algunas personas aún pueden encontrar esto aceptable.
Noctis Skytower
+1 en esto ralentiza considerablemente la ejecución del código. Sugeriría que el autor de esta solución incluya esta información en la respuesta.
Vishal
6
from ctypes import *
pthread = cdll.LoadLibrary("libpthread-2.15.so")
pthread.pthread_cancel(c_ulong(t.ident))

t es suThread objeto.

Lea la fuente de Python ( Modules/threadmodule.cy Python/thread_pthread.h) puede ver que Thread.identes un pthread_ttipo, por lo que puede hacer cualquier cosa que pthreadpueda hacer con el uso de Python libpthread.

snyh
fuente
¿Y cómo haces esto en Windows?
iChux
12
Usted no ni en Windows ni en Linux tampoco. Motivo: el hilo en cuestión puede contener el GIL mientras lo hace (Python libera el GIL cuando llama a C). Si lo hace, su programa se bloqueará instantáneamente. Incluso si no es así, finalmente: los bloques no se ejecutarán, etc., por lo que esta es una idea muy insegura.
Matthias Urlichs
6

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 a la salida del programa. Puede que esta no sea la manera perfecta de terminar hilos / s pero podría ayudar.

Amit Chahar
fuente
Pulgares hacia arriba. Simple de entender
alyssaeliyah
6

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:

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]
tugurio
fuente
Esta es una excelente respuesta que debería estar más arriba en la lista
drootang
¿Por qué elevar SystemExitel after_timeouthilo haría algo al hilo principal (que simplemente está esperando que el primero salga en este ejemplo)?
Davis Herring
@DavisHerring No estoy seguro de a qué te refieres. SystemExit mata el hilo principal, ¿por qué crees que NO HARÍA nada en el hilo principal? Sin esa llamada, el programa continuará esperando en el hilo secundario. También podría presionar ctrl + c o usar cualquier otro medio para eliminar el hilo principal, pero este es un ejemplo.
slumtrimpet
@slumtrimpet: SystemExitsolo 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).
Davis Herring
@DavisHerring Oh, lo tengo. Totalmente de acuerdo contigo. Estaba recordando mal lo que hicimos aquí y malinterpretando su comentario.
slumtrimpet
4

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:

Los hilos del demonio se detienen abruptamente en el apagado. 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 "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).

Chema
fuente
44
No entiendo el último párrafo.
tshepang 01 de
@Tshepang Esto significa que si hay algún ejecutan hilos no daemonic en su aplicación, el intérprete de Python continuará funcionando hasta que todos los no-daemon hilos se realizan . Si no le importa si los subprocesos finalizan cuando finaliza el programa, puede ser útil hacerlos daemon.
Tom Myddeltyn
3

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.

Jason R. Coombs
fuente
1

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 subir SystemExit, finalmente puedes matar 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 código de bytes de Python. Si su hilo llama a una función de bloqueo nativa / incorporada, la excepción se generará solo cuando la ejecución regrese al código python.
    • 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.
  • 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
    es probable que esta característica no sea independiente de la implementación, puede mantenerse
    sin exponer.
wp78de
fuente
0

Esto parece funcionar con pywin32 en Windows 7

my_thread = threading.Thread()
my_thread.start()
my_thread._Thread__stop()
zzart
fuente
0

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.

Paulkernstock
fuente
0

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:

import time
from threading import Thread

def doit(id=0):
    doit.stop=0
    print("start id:%d"%id)
    while 1:
        time.sleep(1)
        print(".")
        if doit.stop==id:
            doit.stop=0
            break
    print("end thread %d"%id)

t5=Thread(target=doit, args=(5,))
t6=Thread(target=doit, args=(6,))

t5.start() ; t6.start()
time.sleep(2)
doit.stop =5  #kill t5
time.sleep(2)
doit.stop =6  #kill t6

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.

rundekugel
fuente
0

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:

from threading import Thread, Event

class KillableThread(Thread):
    def __init__(self, sleep_interval=1, target=None, name=None, args=(), kwargs={}):
        super().__init__(None, target, name, args, kwargs)
        self._kill = Event()
        self._interval = sleep_interval
        print(self._target)

    def run(self):
        while True:
            # Call custom function with arguments
            self._target(*self._args)

        # 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()

if __name__ == '__main__':

    def print_msg(msg):
        print(msg)

    t = KillableThread(10, print_msg, args=("hello world"))
    t.start()
    time.sleep(6)
    print("About to kill thread")
    t.kill()

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í.

Tim Meehan
fuente
0

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:

import sys, threading, time 

class TraceThread(threading.Thread): 
    def __init__(self, *args, **keywords): 
        threading.Thread.__init__(self, *args, **keywords) 
        self.killed = False
    def start(self): 
        self._run = self.run 
        self.run = self.settrace_and_run
        threading.Thread.start(self) 
    def settrace_and_run(self): 
        sys.settrace(self.globaltrace) 
        self._run()
    def globaltrace(self, frame, event, arg): 
        return self.localtrace if event == 'call' else None
    def localtrace(self, frame, event, arg): 
        if self.killed and event == 'line': 
            raise SystemExit() 
        return self.localtrace 

def f(): 
    while True: 
        print('1') 
        time.sleep(2)
        print('2') 
        time.sleep(2)
        print('3') 
        time.sleep(2)

t = TraceThread(target=f) 
t.start() 
time.sleep(2.5) 
t.killed = True

Se detiene después de haber impreso 1y 2. 3No está impreso.

Basj
fuente
-1

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.

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;
usuario1942887
fuente
-1

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
Sud
fuente
-2

Esta es una mala respuesta, mira 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 elthread._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 un quit()método en su clase socket-handle, terminar el socket y luego ejecutar un thread._Thread__stop()interior de su quit().

DoXiD
fuente
Tuve que usar self._Thread__stop () dentro de mi objeto Thread.Thread para que se detuviera. No entiendo por qué un simple self.join () como este ejemplo ( code.activestate.com/recipes/65448-thread-control-idiom ) no funciona.
harijay
12
Más detalles sobre "esto realmente no detiene un hilo" sería útil.
2371
19
Básicamente, llamar al método _Thread__stop no tiene ningún efecto aparte de decirle a Python que el hilo está detenido. De hecho, puede seguir ejecutándose. Ver gist.github.com/2787191 para un ejemplo.
Bluehorn
35
Esto es simplemente incorrecto. _Thread__stop()simplemente marca un hilo como detenido , ¡en realidad no lo detiene! Nunca hagas esto. Tiene una lectura .
dotancohen
-2

Si realmente necesita la capacidad de eliminar una subtarea, use una implementación alternativa. multiprocessingy geventambos 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.

Matthias Urlichs
fuente
2
... y sí, sé que ambos no están estrictamente "enhebrados", pero ambos funcionan si su código se ajusta (o puede ajustarse) a su modelo.
Matthias Urlichs