Multiprocesamiento en Python: Threads a fondo, introducción

Multiprocesamiento en Python: Threads a fondo, introducción
Facebook Twitter Flipboard E-mail

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

Comentarios cerrados
Inicio