domingo, 27 de noviembre de 2011

Objeto Semaphore en Python


Uno de los mecanismos más antiguos de sincronización de hilos son los semáforos (Semaphore). Un semáforo permite acceder a un determinado recurso a un número máximo de hilos simultáneamente. Si hay más hilos que el máximo permitido, los pone en espera y los va dejando pasar según van terminando los que están activos. 

Un semáforo actúa como un contador con un valor inicial. 
  • Cada vez que un hilo llama a Semaphore.acquire(), el contador se decrementa en 1 y se deja pasar al hilo. En el momento que el contador se hace cero, NO se deja pasar al siguiente hilo que llame a acquire(), sino que lo deja bloqueado. 
  • Cada vez que se llama a Semaphore.release(), el contador se incrementa en 1. Si se hace igual a cero, libera al siguiente hilo en la cola de espera.
Los semáforos son parecidos a los candados (locks), pero en vez de tomar el valor 1 y 0, toman n valores que nos indicará la cantidad de hilos que puede tomar el recurso concurrentemente.

Por lo tanto, las llamadas que podremos realizar son:
Semaphore ([value])
El argumento opcional proporciona el valor inicial del contador interno. El valor predeterminado es 1.
acquire ([blocking])
Adquirir un semáforo.
Si se invoca sin argumentos: si el contador interno es superior a cero a la entrada, lo decrementa en una unidad y retorna de inmediato. Si es cero a la entrada, bloquear la ejecución del hilo, esperando a que otro llame a release() para hacerlo mayor de cero. Se gestionan de manera adecuada los interbloqueos, por lo que si hay varias llamadas a acquire() bloqueadas a la espera, release() despertará exactamente a una de ellas. La implementación puede seleccionar una al azar, por lo que no se debe confiar en un orden de respuesta observado. No hay valor de retorno en este caso.
Si se invoca con el argumento blocking a verdadero, hacer lo mismo que si se llama sin argumentos y devolver verdadero.
Si se invoca con blocking a falso, no bloquear. Si una llamada sin argumentos bloqueara, devolver falso de inmediato. En caso contrario, hacer lo mismo que si se llama sin argumentos y devolver verdadero.
release ()
Liberar un semáforo, incrementando su contador interno en una unidad. Si era cero a la entrada y otro hilo está esperando a que sea mayor que cero, despertar a dicho hilo.
Los semáforos sirven para permitir el acceso a un recurso que admite un número máximo de hilos simultáneos. Por ejemplo, si cada hilo abre su conexión a base de datos y sólo queremos un máximo de cinco conexiones abiertas simultáneamente, un semáforo puede ser una opción.
Para ver su funcionamiento basta observar el siguiente ejemplo sencillo. En anteriores entradas del blog podemos observar todo lo relativo al módulo Threading. Por otro lado, se debe crear el semáforo indicando el valor inicial del contador (número máximo de hilos que pueden estar activos simultáneamente) :

import threading
from time import sleep
n_sem = 1
semaforo = threading.Semaphore(n_sem)

class Hilo(threading.Thread):
    def __init__(self, id):
        threading.Thread.__init__(self)
        self.id = id

    def run(self):
        semaforo.acquire()
        print "Hilo %s entra."%(self.id)
        sleep(3)
        semaforo.release()

hilos = [Hilo(1), Hilo(2), Hilo(3)]

for h in hilos:
    h.start()

Finalmente, Python también nos facilita la clase BoundedSemaphore. La diferencia entre Semaphore y BoundedSemaphore es que, cuando se libera el recurso más veces que el n inicial del semáforo en Semaphore cambia dicha cota, mientras que en BoundedSemaphore lo considera un error de ValueError.

Enlances:
http://pyspanishdoc.sourceforge.net/lib/semaphore-objects.html http://chuwiki.chuidiang.org/index.php?title=Hilos_en_python#Semaphore http://pythonr2.wordpress.com/2008/09/01/sincronizacion-de-hilos-en-python/

Primitivas de sincronización mediante cerrojos: Lock y RLock

Dentro del módulo threading, disponemos de las primitivas de sincronización de hilos mediante cerrojos: Lock y RLock.

Lock

Los cerrojos simples son uno de los mecanismos de sincronización que ofrece el módulo threading. Un cerrojo dispone de los métodos acquire (para bloquear el cerrojo) y release (para desbloquear el cerrojo).

Cuando un hilo llama a acquire por primera vez, bloquea el cerrojo de forma que el próximo hilo que llame a acquire quedará esperando hasta que el cerrojo se desbloquee. Llamando a la función release el cerrojo se desbloqueará y el hilo que esté en espera podrá continuar.

Ejemplo:

lock = threading.Lock()

lock.acquire() # El hilo queda esperando si el cerrojo ha sido bloqueado
# Acceso a recurso compartido
lock.release()


RLock (Re-entrant Lock)

Un cerrojo reentrante funciona de forma similar a un cerrojo simple, pero en este caso un mismo hilo puede llamar a acquire varias veces sin quedar esperando.

Cuando un hilo bloquea un cerrojo, este hilo puede volver a llamar a acquire tantas veces como quiera sin que quede esperando, esto se debe a que esta implementación de cerrojos tiene en cuenta al hilo que ha bloqueado el cerrojo e impide que éste pueda quedar esperando tras un cerrojo que él mismo ha bloqueado.

Si otro hilo distinto al que ha bloqueado el cerrojo intenta llamar a acquire, quedará esperando hasta que el hilo correspondiente lo libere llamando a release.

Para liberar el cerrojo, se deberá llamar a release tantas veces como se haya llamado a acquire.

Ejemplo:

lock = threading.RLock()

lock.acquire()
lock.acquire() # El hilo no quedará esperando

lock.release()
lock.release()



Fuentes:

viernes, 25 de noviembre de 2011

Hilos en Python

Para comenzar, vamos a ver una definición de hilo, que según Wikipedia es la unidad de procesamiento más pequeña que puede ser planificada por un sistema operativo y permite a una aplicación realizar varias tareas a la vez (concurrentemente). Los distintos hilos de ejecución comparten una serie de recursos tales como el espacio de memoria, los archivos abiertos, situación de autenticación, etc. Esta técnica permite simplificar el diseño de una aplicación que debe llevar a cabo distintas funciones simultáneamente.

En el artículo que nos ocupa, vamos a analizar cómo implementar y utilizar los hilos en Python. El módulo que vamos a utilizar es el módulo threading por lo que habrá que impotarlo al comienzo de nuestros programas con la línea:
import threading