Python | Diferentes formas de matar un hilo

En general, matar subprocesos abruptamente se considera una mala práctica de programación. Cerrar un subproceso abruptamente puede dejar abierto un recurso crítico que debe cerrarse correctamente. Pero es posible que desee eliminar un hilo una vez que haya pasado un período de tiempo específico o se haya generado alguna interrupción. Existen varios métodos por los cuales puede matar un hilo en python. 
 

  • Generar excepciones en un hilo de python
  • Establecer/restablecer indicador de parada
  • Usar rastros para matar hilos
  • Usando el módulo de multiprocesamiento para matar hilos
  • Matar el hilo de Python configurándolo como demonio
  • Usando una función oculta _stop()

Generación de excepciones en un subproceso de Python: 
este método utiliza la función PyThreadState_SetAsyncExc() para generar una excepción en un subproceso. Por ejemplo, 
 

Python3

# Python program raising
# exceptions in a python
# thread
 
import threading
import ctypes
import time
  
class thread_with_exception(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name
             
    def run(self):
 
        # target function of the thread class
        try:
            while True:
                print('running ' + self.name)
        finally:
            print('ended')
          
    def get_id(self):
 
        # returns id of the respective thread
        if hasattr(self, '_thread_id'):
            return self._thread_id
        for id, thread in threading._active.items():
            if thread is self:
                return id
  
    def raise_exception(self):
        thread_id = self.get_id()
        res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id,
              ctypes.py_object(SystemExit))
        if res > 1:
            ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
            print('Exception raise failure')
      
t1 = thread_with_exception('Thread 1')
t1.start()
time.sleep(2)
t1.raise_exception()
t1.join()

Cuando ejecutemos el código anterior en una máquina, notará que tan pronto como se llame a la función raise_exception(), la función de destino run() finaliza. Esto se debe a que tan pronto como se genera una excepción, el control del programa salta del bloque try y la función run() finaliza. Después de eso, se puede llamar a la función join() para matar el hilo. En ausencia de la función run_exception(), la función de destino run() sigue ejecutándose para siempre y nunca se llama a la función join() para eliminar el subproceso. 
  
Establecer/restablecer indicador de detención: 
para eliminar un subproceso, podemos declarar un indicador de detención y este indicador será verificado ocasionalmente por el subproceso. Por ejemplo 
 

Python3

# Python program showing
# how to kill threads
# using set/reset stop
# flag
 
import threading
import time
 
def run():
    while True:
        print('thread running')
        global stop_threads
        if stop_threads:
            break
 
stop_threads = False
t1 = threading.Thread(target = run)
t1.start()
time.sleep(1)
stop_threads = True
t1.join()
print('thread killed')

En el código anterior, tan pronto como se establece la variable global stop_threads, la función de destino run() finaliza y el subproceso t1 se puede eliminar utilizando t1.join(). Pero uno puede abstenerse de usar la variable global debido a ciertas razones. Para esas situaciones, los objetos de función se pueden pasar para proporcionar una funcionalidad similar a la que se muestra a continuación. 
 

Python3

# Python program killing
# threads using stop
# flag
 
import threading
import time
 
def run(stop):
    while True:
        print('thread running')
        if stop():
                break
                 
def main():
        stop_threads = False
        t1 = threading.Thread(target = run, args =(lambda : stop_threads, ))
        t1.start()
        time.sleep(1)
        stop_threads = True
        t1.join()
        print('thread killed')
main()

El objeto de función pasado en el código anterior siempre devuelve el valor de la variable local stop_threads. Este valor se verifica en la función run(), y tan pronto como se restablece stop_threads, la función run() finaliza y el subproceso se puede eliminar. 
  
Usar rastros para matar hilos: 
este método funciona instalando rastros en cada hilo. Cada rastro termina en la detección de algún estímulo o bandera, matando así instantáneamente el hilo asociado. Por ejemplo 
 

Python3

# Python program using
# traces to kill threads
 
import sys
import trace
import threading
import time
class thread_with_trace(threading.Thread):
  def __init__(self, *args, **keywords):
    threading.Thread.__init__(self, *args, **keywords)
    self.killed = False
 
  def start(self):
    self.__run_backup = self.run
    self.run = self.__run     
    threading.Thread.start(self)
 
  def __run(self):
    sys.settrace(self.globaltrace)
    self.__run_backup()
    self.run = self.__run_backup
 
  def globaltrace(self, frame, event, arg):
    if event == 'call':
      return self.localtrace
    else:
      return None
 
  def localtrace(self, frame, event, arg):
    if self.killed:
      if event == 'line':
        raise SystemExit()
    return self.localtrace
 
  def kill(self):
    self.killed = True
 
def func():
  while True:
    print('thread running')
 
t1 = thread_with_trace(target = func)
t1.start()
time.sleep(2)
t1.kill()
t1.join()
if not t1.isAlive():
  print('thread killed')

En este código, start() se modifica ligeramente para configurar la función de seguimiento del sistema mediante settrace() . La función de seguimiento local se define de tal manera que, cada vez que se establece el indicador de eliminación (eliminado) del subproceso respectivo, se genera una excepción SystemExit en la ejecución de la siguiente línea de código, que finaliza la ejecución de la función de destino. Ahora el hilo se puede eliminar con join(). 
  
Uso del módulo de multiprocesamiento para matar subprocesos: 
el módulo de multiprocesamiento de Python le permite generar procesos de la misma manera que genera subprocesos utilizando el módulo de subprocesos. La interfaz del módulo de subprocesos múltiples es similar a la del módulo de subprocesos. Por ejemplo, en un código dado, creamos tres hilos (procesos) que cuentan del 1 al 9. 
 

Python3

# Python program creating
# three threads
 
import threading
import time
 
# counts from 1 to 9
def func(number):
    for i in range(1, 10):
        time.sleep(0.01)
        print('Thread ' + str(number) + ': prints ' + str(number*i))
 
# creates 3 threads
for i in range(0, 3):
    thread = threading.Thread(target=func, args=(i,))
    thread.start()

La funcionalidad del código anterior también se puede implementar usando el módulo de multiprocesamiento de manera similar, con muy pocos cambios. Consulte el código que figura a continuación. 
 

Python3

# Python program creating
# thread using multiprocessing
# module
 
import multiprocessing
import time
 
def func(number):
    for i in range(1, 10):
        time.sleep(0.01)
        print('Processing ' + str(number) + ': prints ' + str(number*i))
 
for i in range(0, 3):
    process = multiprocessing.Process(target=func, args=(i,))
    process.start()

Aunque la interfaz de los dos módulos es similar, los dos módulos tienen implementaciones muy diferentes. Todos los subprocesos comparten variables globales, mientras que los procesos están completamente separados entre sí. Por lo tanto, matar procesos es mucho más seguro que matar hilos. A la clase Process se le proporciona un método, terminar() , para eliminar un proceso. Ahora, volviendo al problema inicial. Supongamos que en el código anterior, queremos eliminar todos los procesos después de que hayan pasado 0.03 s. Esta funcionalidad se logra usando el módulo de multiprocesamiento en el siguiente código. 
 

Python3

# Python program killing
# a thread using multiprocessing
# module
 
import multiprocessing
import time
 
def func(number):
    for i in range(1, 10):
        time.sleep(0.01)
        print('Processing ' + str(number) + ': prints ' + str(number*i))
 
# list of all processes, so that they can be killed afterwards
all_processes = []
 
for i in range(0, 3):
    process = multiprocessing.Process(target=func, args=(i,))
    process.start()
    all_processes.append(process)
 
# kill all processes after 0.03s
time.sleep(0.03)
for process in all_processes:
    process.terminate()

Aunque los dos módulos tienen diferentes implementaciones. Esta funcionalidad proporcionada por el módulo de multiprocesamiento en el código anterior es similar a eliminar hilos. Por lo tanto, el módulo de multiprocesamiento se puede usar como una alternativa simple siempre que se requiera implementar la eliminación de hilos en Python. 
  
Matar el subproceso de Python configurándolo como daemon: los  
subprocesos de daemon son aquellos subprocesos que se eliminan cuando se cierra el programa principal. Por ejemplo 
 

Python3

import threading
import time
import sys
 
def func():
    while True:
        time.sleep(0.5)
        print("Thread alive, and it won't die on program termination")
 
t1 = threading.Thread(target=func)
t1.start()
time.sleep(2)
sys.exit()

Tenga en cuenta que el subproceso t1 permanece vivo y evita que el programa principal salga a través de sys.exit(). En Python, cualquier subproceso vivo que no sea un demonio bloquea el programa principal para salir. Mientras que los subprocesos de daemon se eliminan tan pronto como se cierra el programa principal. En otras palabras, tan pronto como se cierra el programa principal, se eliminan todos los subprocesos del daemon. Para declarar un subproceso como daemon, establecemos el argumento de la palabra clave daemon como True. Por ejemplo, en el código dado, demuestra la propiedad de los subprocesos de daemon. 
 

Python3

# Python program killing
# thread using daemon
 
import threading
import time
import sys
 
def func():
    while True:
        time.sleep(0.5)
        print('Thread alive, but it will die on program termination')
 
t1 = threading.Thread(target=func)
t1.daemon = True
t1.start()
time.sleep(2)
sys.exit()

Tenga en cuenta que, tan pronto como se cierra el programa principal, el subproceso t1 se elimina. Este método demuestra ser extremadamente útil en los casos en que la finalización del programa se puede utilizar para desenstringr la eliminación de subprocesos. Tenga en cuenta que en Python, el programa principal finaliza tan pronto como todos los subprocesos que no son daemon están muertos, independientemente de la cantidad de subprocesos daemon vivos. Por lo tanto, es posible que los recursos retenidos por estos subprocesos daemon, como archivos abiertos, transacciones de bases de datos, etc., no se liberen correctamente. El subproceso de control inicial en un programa de python no es un subproceso de daemon. No se recomienda cerrar un hilo a la fuerza a menos que se sepa con certeza que hacerlo no causará fugas ni interbloqueos. 
Usando una función oculta _stop() : 
Para eliminar un hilo, usamos la función oculta _stop(). Esta función no está documentada pero podría desaparecer en la próxima versión de Python. 
 

Python3

# Python program killing
# a thread using ._stop()
# function
 
import time
import threading
 
class MyThread(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(MyThread, self).__init__(*args, **kwargs)
        self._stop = threading.Event()
 
    # function using _stop function
    def stop(self):
        self._stop.set()
 
    def stopped(self):
        return self._stop.isSet()
 
    def run(self):
        while True:
            if self.stopped():
                return
            print("Hello, world!")
            time.sleep(1)
 
t1 = MyThread()
 
t1.start()
time.sleep(5)
t1.stop()
t1.join()

Nota: Es posible que los métodos anteriores no funcionen en una situación u otra, porque Python no proporciona ningún método directo para eliminar hilos.
 

Publicación traducida automáticamente

Artículo escrito por Manthanchauhan y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *