TECNICAS DE SINCRONIZACION:
- SEMAFOROS
- BARRERAS
Sincronización de hilos en python
Mutex o locks o CandadosRealmente los mutex son la herramienta más fácil que hay de sintonización. Un mutex se usa en programación concurrente para que un solo procesos o hilo en nuestro caso pueda tomar un recurso compartido por otros. La parte de código que toma dicho recurso se llama sección crítica. El mutex solo puede tomar dos estados que son tomados y liberado. Miremos un ejemplo para aclarar las ideas.
En el archivo mutex.py y compare su salida con el de hilo.py de la entrada anterior
#!/usr/bin/python # Nombre de Fichero : mutex.py import threading from time import sleep mutex = threading.Lock() class Hilo(threading.Thread): def __init__(self, id): threading.Thread.__init__(self) self.id = id def run(self): mutex.acquire() sleep(3-self.id) print "Yo soy %s la variable d tiene el valor %s"%(self.id,d) mutex.release() d=1; hilos = [Hilo(1), Hilo(2), Hilo(3)] for h in hilos: h.start()
locks re-entrantes – Rlook
La clase RLock es similar a Lock, pero puede ser adquirido por el mismo thread varias veces, y no quedará liberado hasta que el thread lo libere tantas veces como llamó a acquire.
Los semáforos
Los semáforos son parecidos a los mutex pero en vez de tomar el valor 1 y 0, toman n valores que nos indicara la cantidad de hilos, que puede tomar el recurso concurrentemente. Para esto python nos facilita la clase Semaphore y BoundedSemaphore. La diferencia de 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
Miremos este ejemplo llamado semaphore.py
#!/usr/bin/python # Nombre de Fichero : semaphore.py import threading from time import sleep semaforo = threading.Semaphore(2) n=0 class Hilo(threading.Thread): def __init__(self, id): threading.Thread.__init__(self) self.id = id def run(self): semaforo.acquire() sleep(3-self.id) d.append(self.id) semaforo.release() d=[]; hilos = [Hilo(1), Hilo(2), Hilo(3)] for h in hilos: h.start() sleep(4) semaforo.acquire() print d semaforo.release()fijese que si agrega justo antes del sleep(4) 3 release como estos
semaforo.release() semaforo.release() semaforo.release()La salida cambia de orden ya que entra mas de uno hilo concurrente.
En cambio si después de agregar estas tres lineas, cambiamos la linea
semaforo = threading.Semaphore(2) por semaforo = threading.BoundedSemaphore(2)
dara error de tipo ValueError.
Las condiciones
Las condiciones son de utilidad para hacer que los threads sólo puedan entrar en la sección crítica de darse una cierta condición o evento.La clase condition tiene métodos wait, notify y notifyAll.
El método wait debe llamarse después de haber adquirido el objeto condiction con acquire. Este método libera el candado y bloquea al hilo hasta que una llamada a notify o notifyAll en otro hilo le indican que se ha cumplido la condición. El hilo que informa a los demás de que se ha producido la condición, también debe llamar a acquire antes de llamar a notify o notifyAll.
Espero que el ejemplo del archivo condition.py sea ilustrativo.
#!/usr/bin/python # Nombre de Fichero : condition.py import threading cond = threading.Condition() class Cliente (threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): while True: cond.acquire() cond.wait() mesa.pop() cond.notify() cond.release() class Cosinero(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): while True: cond.acquire() if len(mesa) != 0: cond.wait() mesa.append("Torta de frutillas") cond.notify() cond.release() print "El bar" mesa = [] cliente = Cliente() cosinero = Cosinero() cliente.start() cosinero.start() while True: print mesaacquire no bloqueante
Para que acquire no bloque el hilo le tenemos que pasar (False) como parámetro. Dicha invocación nos dará el resultado de si obtuvo el hilo o no. Modificaremos el ejemplo del mutex para que se vea lo que pasa.
#!/usr/bin/python # Nombre de Fichero : mutex2.py import threading from time import sleep mutex = threading.Lock() class Hilo(threading.Thread): def __init__(self, id): threading.Thread.__init__(self) self.id = id def run(self): b = mutex.acquire(False) print b sleep(3-self.id) print "Yo soy %s la variable d tiene el valor %s"%(self.id,d) if b :mutex.release() d=1; hilos = [Hilo(1), Hilo(2), Hilo(3)] for h in hilos: h.start()Los eventos
La clase Event es un wrapper por encima de Condition y sirven principalmente para coordinar threads mediante señales que indican los eventos que han ocurrido. Los no tienen los métodos acquire y release.
El hilo que debe esperar el evento se pondra en espera por medio de la llama al método wait y se bloquea, le podriamos pasar como parámetro un número en coma flotante indicando el número máximo de segundos a esperar. Otro hilo, cuando ocurre el evento, envía la señal a los hilos bloqueados a la espera de dicho evento utilizando el método set. Los hilos que estaban esperando se desbloquean una vez recibida la señal. La bandera que indica si se ha producido el evento se puede volver a setar a falso usando clear. Los eventos son similares a las condiciones.
Un ejemplo lo vemos en eventos.py que es una modificación de condition.py
#!/usr/bin/python # Nombre de Fichero : eventos.py import threading evento = threading.Event() class Cliente (threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): while True: self.evento.wait() mesa.pop() class Cosinero(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): while True: if len(mesa) != 0: cond.wait() mesa.append("Torta de frutillas") evento.set() print "El bar" mesa = [] cliente = Cliente() cosinero = Cosinero() cliente.start() cosinero.start() while True: print mesa
Barreras
Los locks, también llamados mutex
son objetos con dos estados posibles: adquirido o libre. Cuando un thread
adquiere la barrera, los demás threads que pidan adquirirlo se bloquearán hasta
que el thread que lo ha adquirido libere la barrera, momento en el cual podrá
entrar otro thread.
La barrera se representa mediante
la clase Lock. Llamando a Lock.acquire() el hilo bloqueará el Lock, de forma
que el siguiente hilo que llame a Lock.acquire() se quedará a la espera de que
el Lock se desbloquee. La llamada a Lock.release() desbloquea el Lock, haciendo
que el hilo que estaba en espera continúe. Un ejemplo se muestra a continuación:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| from threading import Thread, Lock import time class MiHilo(Thread): def __init__ ( self , inicio, fin, bloqueo): Thread.__init__( self ) self .inicio = inicio self .fin = fin self .bloqueo = bloqueo def run ( self ): bloqueo.acquire() for i in range ( self .inicio, self .fin): print ( "contador = " + str (i)) time.sleep( 0.2 ) bloqueo.release() if __name__ = = '__main__' : bloqueo = Lock() bloqueo.acquire() hilo = MiHilo( 0 , 10 , bloqueo) hilo.start() time.sleep( 1 ) for i in range ( 10 , 20 ): print ( "main = " + str (i)) time.sleep( 0.1 ) bloqueo.release() |
Esta clase Lock es muy simple.
Cualquier hilo, puede liberar a otro sin importar si lo haya bloquedado o no, además
si un hilo llama él mismo dos veces a acquire(), se queda bloqueado en la
segunda llamada. Una mejor opción es RLock. En esta barrera solo el que haya
llamado al método acquire() puede liberarlo con el método release() y un mismo
hilo puede llamar varias veces a acquire() sin quedarse bloqueado, pero tiene
que hacer el mismo número de llamadas a release() para desbloquearlo. El
ejemplo siguiente muestra su funcionamiento:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| from threading import Thread, RLock import time class MiHilo(Thread): def __init__ ( self , inicio, fin, bloqueo): Thread.__init__( self ) self .inicio = inicio self .fin = fin self .bloqueo = bloqueo def run ( self ): bloqueo.acquire() for i in range ( self .inicio, self .fin): print ( "contador = " + str (i)) time.sleep( 0.2 ) bloqueo.release() if __name__ = = '__main__' : bloqueo = RLock() bloqueo.acquire() hilo = MiHilo( 0 , 10 , bloqueo) hilo.start() time.sleep( 1 ) for i in range ( 10 , 20 ): print ( "main = " + str (i)) bloqueo.acquire() time.sleep( 0.1 ) bloqueo.release() print ( "El hilo todavia no ha comenzado" ) for i in range ( 10 , 20 ): bloqueo.release() |
REFERENCIAS
- https://pythonr2.wordpress.com/tag/semaforos-en-python/
- http://www.solveet.com/barreras