martes, 13 de octubre de 2015

PROGRAMACION EN EL MODELO DE MEMORIA COMPARTIDA

TECNICAS DE SINCRONIZACION:
  • SEMAFOROS
  • BARRERAS

Sincronización de hilos en python

Mutex o locks o Candados
Realmente 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 mesa
acquire 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