En anteriores artículos de la serie hemos hablado sobre la diferencia entre multi hilo y multi proceso en CPython y sobre el Global Interpreter Lock y como esquivarlo en nuestras aplicaciones.
En esta nueva entrega de multiprocesamiento en Python vamos a iniciar un recorrido a fondo por los hilos y su uso hasta que no tengan secretos para nosotros. En este primer post vamos a hacer una ligera introducción.
Los hilos permiten a nuestras aplicaciones ejecutar múltiples operaciones de forma concurrente en el mismo espacio de proceso. El módulo utilizado para ello es el módulo threading
.
El objeto Thread
El modo más sencillo para usar un hilo es instanciar un objeto de la clase Thread
con una función objetivo y hacer una llamada a su método start()
.
import threading def worker(): """funcion que realiza el trabajo en el thread""" print 'Estoy trabajando para Genbeta Dev' return threads = list() for i in range(3): t = threading.Thread(target=worker) threads.append(t) t.start()La salida del código anterior devolverá tres impresiones de la cadena "Estoy trabajando para Genbeta Dev".
A los threads se les puede pasar parámetros que después son usados por la función objetivo. Cualquier tipo de objeto puede ser pasado como parámetro a un thread.
import threading def worker(count): """funcion que realiza el trabajo en el thread""" print "Este es el %s trabajo que hago hoy para Genbeta Dev" % count return threads = list() for i in range(3): t = threading.Thread(target=worker, args=(i,)) threads.append(t) t.start()La salida del anterior ejemplo sería como la que sigue:
Este es el 0 trabajo que hago hoy para Genbeta Dev Este es el 1 trabajo que hago hoy para Genbeta Dev Este es el 2 trabajo que hago hoy para Genbeta Dev
Saber en que Thread nos encontramos
Se pueden usar argumentos para nombrar los threads que creamos aunque no es necesario. Cada instancia de la clase Thread
tiene un nombre asigndo por defecto.
Nombrar los threads puede ser útil por ejemplo, a la hora de clarificar nuestro código.
import threading import time def worker(): print threading.currentThread().getName(), 'Lanzado' time.sleep(2) print threading.currentThread().getName(), 'Deteniendo' def servicio(): print threading.currentThread().getName(), 'Lanzado' print threading.currentThread().getName(), 'Deteniendo' t = threading.Thread(target=servicio, name='Servicio') w = threading.Thread(target=worker, name='Worker') z = threading.Thread(target=worker) w.start() z.start() t.start()La salida del código anterior sería:
Worker Lanzado Thread-1 Lanzado Servicio Lanzado Worker Deteniendo Thread-1 Deteniendo Servicio Deteniendo
Usando el módulo logging
Si vamos a depurar o logear algo relacionado con los threads lo mejor es que utilicemos el módulo logging
para ello.
import threading import logging import time logging.basicConfig( level=logging.DEBUG, format='[%(levelname)s] - %(threadName)-10s : %(message)s') def worker(): logging.debug('Lanzado') time.sleep(2) logging.debug('Deteniendo') w = threading.Thread(target=worker, name='Worker') w.start()El módulo logging soporta la inclusión del nombre del hilo de forma nativa, la salida de uno de los mensajes anteriores sería parecido a esto:
[DEBUG] - Worker : Lanzado
Daemon Threads
En los ejemplos anteriores, la aplicación espera antes de acabar a que todos sus threads se hayan completado. Algunas veces querremos lanzar un thread como un daemon que se ejecuta sin bloquear el hilo principal de la aplicación permitiéndole salir en cualquier momento.
Este comportamiento es útil para servicios en los que puede que detener un hilo no sea una tarea trivial o en la que permitir que un hilo muera en mitad de un proceso no constituya una corrupción de datos. Por ejemplo, un proceso puede iniciar un hilo que haga algún tipo de ping heartbeat para monitorizar un servicio.
Para levantar un thread como daemon solo tenemos que invocar a su método setDaemon()
pasándole el argumento True
.
import threading import logging import time logging.basicConfig( level=logging.DEBUG, format='[%(levelname)s] - %(threadName)-10s : %(message)s') def daemon(): logging.debug('Lanzado') time.sleep(2) logging.debug('Deteniendo') d = threading.Thread(target=daemon, name='Daemon') d.setDaemon(True) d.start()Si ejecutáramos el ejemplo anterior, no recibiríamos el mensaje de salida del thread puesto que la aplicación saldría sin esperarlo. Cuando el proceso termina todos los hilos son eliminados. Para esperar a que un thread daemon termine, debemos invocar explícitamente al método
join()
.
Para saber si un thread esta vivo, podemos utilizar el método isAlive()
en él, que devolverá True
o False
según su estado.
Conclusión
Hoy hemos un hecho un primer contacto con el módulo threading
de Python y hemos aprendido a usar el objeto logging para usar mensajes de depurado con nuestros hilos. En el siguiente artículo trataremos temas como la enumeración de hilos, la herencia directa del objeto Thread
y los threads con temporizador.
En Genbeta Dev | Multiprocesamiento en Python