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.
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:
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/