Change Surfers

Gestión de hilos en Python

Python ha demostrado ser un lenguaje de programación muy sencillo, ordenado e intuitivo. El módulo threading y sus principales métodos nos dan la clave para administrar los hilos en Python y sacarles todo su provecho de una forma simple y eficiente.

En programación el uso de hilos es una técnica que permite que una aplicación ejecute en el mismo espacio de proceso varias operaciones del sistema operativo, es decir, de forma concurrente. Utilizando esta técnica se crean subconjuntos de los procesos que comparten memoria y recursos, pero que ocupan una dirección de memoria diferente. De esta forma, se ahorra bastante tiempo en ejecución y se consigue que los programas sean más eficientes. Una vez terminado el último hilo, termina el proceso y se liberan los recursos utilizados hasta ese momento.

# Librería para el uso de los hilos en Python
import threading

Lo recomendable para poder manejar todas las funciones y características de hilos en Python de una forma cómoda, es crear una clase y utilizar el módulo threading que es de alto nivel, más completo, orientado a objetos y en el que viene ya incluido la clase thread de bajo nivel que representa un hilo. Aunque también es posible crear una instancia de la clase thread directamente e indicar como parámetro una función a ejecutar y hacer la llamada a su método start(). En el siguiente ejemplo se ve cómo se crean dos hilos que cuentan hasta diez cada uno de esta segunda forma.

import threading
def contar():
    '''Contar hasta diez”
    contador = 0
    while contador<10:
        contador+=1
        print('Hilo:', 
              threading.current_thread().getName(), 
              'Contador:', contador)
hilo1 = threading.Thread(target=contar)
hilo2 = threading.Thread(target=contar)
hilo1.start()
hilo2.start()

Así de simple. Pero si lo queremos es ganar flexibilidad, la mejor opción es extender la clase thread reescribiendo el método run() y el constructor __init__() para pasarle los parámetros que queramos. Una vez creado el objeto hilo, con el método start() iniciaremos su actividad que provocará una llamada en el método run() en un hilo de control por separado. 

import threading
class MiHilo(threading.Thread):
   def run():
      #codigo del hilo
# Arranque del hilo
hilo = MiHilo()
hilo.start()

 

A continuación, vemos un ejemplo sencillo en el que se crean 10 hilos y cada uno imprime su número de hilo. En este caso, se ha puesto un constructor para recibir el parámetro del hilo que es y, por lo tanto, debemos llamar explícitamente al constructor de la clase padre thread.

import threading
# Clase hilo
class hilo(threading.Thread):
    def __init__(self,x): #inicializamos el hilo
        self.__x = x
        threading.Thread.__init__(self)
    def run (self):  # run() se utiliza para definir el comportamiento del hilo
          print(str(self.__x)) #imprimimos el valor del hilo que es
# Inicia 10 hilos.
for i in range(10): #Bucle iterativo para iniciar los 10 hilos
    hilo(i).start()

En ambos casos, un hilo puede poseer un nombre identificativo. Este nombre es una cadena que no tiene significado propio y que, incluso, puede tener el mismo valor para varios hilos a la vez. El nombre se puede pasar en el constructor directamente o asignar con el método setName() y recuperar con el método getName().

Si lo que queremos es que nuestro programa principal termine mientras los hilos hijos siguen en ejecución -lo que se conoce como hilos de daemon o de demonio- deberemos asignar true al argumento daemon al crear el objeto thread, o bien lo estableceremos con posterioridad con el método set_daemon(). Este tipo de hilos son útiles para programas de monitorización o chequeo de recursos o servicios que permiten que sigan en un segundo plano. Para esperar que un subproceso demonio complete su trabajo se utilizará el método join() que por defecto lo bloqueará indefinidamente. También es posible pasarle un valor flotante que será el número de segundos que lo esperará el hilo principal.  

Una vez realizada la llamada, el hilo se considera vivo y activo ya que ha iniciado su actividad. Una vez finalice la ejecución del método run() bien porque acabe o bien debido a que salte una excepción, dejará de estar vivo y activo. Una llamada muy útil para comprobar el estado de un hilo es el método isAlive() que devuelve true en caso afirmativo, o el método enumerate() que devuelve una lista con los hilos vivos. 

Estas líneas y estos métodos han sido unas pinceladas básicas de cómo empezar a trabajar con hilos en Python y con el módulo threading. Pero sin duda para seguir aprendiendo, debemos enfrentarnos a uno de los mayores problemas que tiene trabajar con hilos y con la concurrencia: la sincronización al acceder a los mismos recursos.