UNIDAD III

Programación Concurrente

Profesor Miguel Cantillana

INS240 – Lenguajes de Programación
Ingeniería en Computación e Informática
Semestre 2017-1

Sincronización de Thread

Sincronización de Threads

Control del acceso a los recursos. Bloqueos (Lock)

  • Nos permite controlar el acceso de los hilos a los recursos compartidos (variables, listas, diccionarios, etc.)
  • Evitar la corrupción o pérdida de datos

 

Control del acceso a los recursos. Bloqueos (Lock)

  • Los objetos Lock() permiten gestionar los bloqueos que evitan que los hilos modifiquen variables compartidas al mismo tiempo.
  • El método acquire() permite que un hilo bloquee a otros hilos en un punto del programa
  • El método release() libera el bloqueo. En el momento que se produzca el desbloqueo otro hilo (o el mismo) podrá bloquear de nuevo. 

 

Control del acceso a los recursos. Bloqueos (Lock)

Control del acceso a los recursos. Bloqueos (ii)

import threading

total = 0

def acumula5():
    global total
    contador = 0
    hilo_actual = threading.current_thread().getName()
    while contador < 20:
        print('Esperando para bloquear', hilo_actual)
        bloquea.acquire()
        try:
            contador = contador + 1
            total = total + 5
            print('Bloqueado por', hilo_actual, contador)
            print('Total', total)
            
        finally:
            print('Liberado bloqueo por', hilo_actual)
            bloquea.release()
    
bloquea = threading.Lock()    
hilo1 = threading.Thread(name='h1', target=acumula5)
hilo2 = threading.Thread(name='h2', target=acumula5)
hilo1.start()
hilo2.start()
thread_14.py

Control del acceso a los recursos. Bloqueos (iii)

  • Para conocer si otro hilo ha adquirido el bloqueo sin mantener al resto de subprocesos detenidos hay que asignar al argumento blocking de acquire() el valor False.
  • De esta forma se pueden realizar otros trabajos mientras se espera a tener éxito en un bloqueo y controlar el número de reintentos realizados.
  • El método locked() se puede utilizar para verificar si un bloqueo se mantiene en un momento dado

 

Control del acceso a los recursos. Bloqueos (iii)

import threading

def acumula5():
    global total
    contador = 0
    hilo_actual = threading.current_thread().getName()
    num_intentos = 0
    while contador < 20:
        lo_consegui = bloquea.acquire(blocking=False)
        try:
            if lo_consegui:
                contador = contador + 1
                total = total + 5
                print('Bloqueado por', hilo_actual, contador)
                print('Total', total,hilo_actual)
            else:
                num_intentos+=1
                print('Número de intentos de bloqueo', 
                      num_intentos,
                      'hilo',
                      hilo_actual, 
                      bloquea.locked())
                print(hilo_actual,'Hacer otro trabajo')
            
        finally:
            if lo_consegui:
                print('Liberado bloqueo por', hilo_actual)
                bloquea.release()
    
total = 0
bloquea = threading.Lock()    
hilo1 = threading.Thread(name='h1', target=acumula5)
hilo2 = threading.Thread(name='h2', target=acumula5)
hilo1.start()
hilo2.start()
thread_15.py

Sincronizar hilos con objetos Event

La forma más fácil de hacer que un hilo espere a que otro hilo le avise es por medio de Event. 

  • El Event tiene un flag interno que indica si un hilo puede continuar o no.
  • Un hilo llama al método Event.wait() y se queda bloqueado en espera hasta que el flag interno de Event se ponga a True.
  • Otro hilo llame a Event.set() para poner el flag a True o bien a Event.clear() para ponerlo a False.

Sincronizar hilos con objetos Event

import threading, random, logging

logging.basicConfig( level=logging.DEBUG,
    format='[%(levelname)s] - %(threadName)-10s : %(message)s')


def gen_pares():
    num_pares = 0
    logging.debug('Numeros pares: ')
    while num_pares < 25:
        numero = random.randint(1, 10)
        resto = numero % 2
        if resto == 0:
            num_pares +=1
            logging.debug('Numero par %s: ',str(numero))


def contar():
    contar = 0
    nom_hilo = threading.current_thread().getName()
    # print(nom_hilo, "en espera")
    logging.debug("En espera")
    estado = evento.wait()
    while contar < 25:
        contar+=1
        # print(nom_hilo, ':', contar)
        logging.debug('contar: %d',contar)
evento = threading.Event()
hilo1 = threading.Thread(name='h1', target=contar)
hilo2 = threading.Thread(name='h2', target=contar)
hilo1.start()
hilo2.start()

logging.debug('Obteniendo 25 números pares...')
gen_pares()
logging.debug('Ya se han obtenido')
evento.set()
thread_16.py

Sincronizar hilos con objetos Event (ii)

import threading

def avanza(evento):
    ciclo = 0
    valor = 0
    while valor < 20:
        estado = evento.wait()
        if estado:
            ciclo+=1
            valor+=1
            print('avanza', valor)
            if ciclo == 10 and hilo2.isAlive():
                evento.clear()
                ciclo = 0
    print('avanza: ha finalizado')

def retrocede(evento, tiempo):
    ciclo = 0
    valor = 21
    while valor > 1:
        estado = evento.wait(tiempo)
        if not estado:
            ciclo+=1
            valor-=1
            print('retrocede', valor)
            if ciclo == 5 and hilo1.isAlive():
                evento.set()
                ciclo = 0
    print('retrocede: ha finalizado')
            
evento = threading.Event()
hilo1 = threading.Thread(target=avanza, 
                         args=(evento,),)
hilo2 = threading.Thread(target=retrocede, 
                         args=(evento, 0.5),)
hilo1.start()
hilo2.start()
thread_17.py

Sincronizar hilos con objetos Condition

  • los bloqueos suelen estar vinculados con unas operaciones que se tienen que realizar antes que otras.

 

Sincronizar hilos con objetos Condition

  • El hilo que debe esperar por los datos, debe llamar al método Condition.acquire() y luego al Condition.wait().
  • Para llamar a wait() es obligatorio ser el propietario de la Condition, cosa que se consigue llamando a acquire().
  • La llamada a wait() libera la Condition, pero deja al hilo bloqueado hasta que alguien llame a Condition.notify().

 

Sincronizar hilos con objetos Condition

  • El hilo encargado de suministrar los datos, debe llamar a Condition.acquire() para hacerse dueño de la Condition y cuando los datos estén disponibles, llamar a Condition.notify() y luego a Condition.release().
  • Estas dos llamadas juntas despertarán al hilo a la espera de datos.
  • La llamada a notify() no libera la Condition, por lo que el hilo que está en el wait() será notifiado, pero no comenzará su ejecución hasta que se llame a release().

 

Sincronizar hilos con objetos Condition (i)

import threading
import time
import logging

logging.basicConfig(level=logging.DEBUG,
                    format='(%(threadName)-9s) %(message)s',)

def consumer(cv):
    logging.debug('Consumer thread started ...')
    # with cv:
    cv.acquire()
    logging.debug('Consumer waiting ...')
    cv.wait()
    logging.debug('Consumer consumed the resource')
    cv.release()    


def producer(cv):
    logging.debug('Producer thread started ...')
    # with cv:
    cv.acquire()
    logging.debug('Making resource available')
    logging.debug('Notifying to all consumers')
    cv.notifyAll()
    cv.release()    

if __name__ == '__main__':
    condition = threading.Condition()
    cs1 = threading.Thread(name='consumer1', target=consumer, args=(condition,))
    cs2 = threading.Thread(name='consumer2', target=consumer, args=(condition,))
    pd = threading.Thread(name='producer', target=producer, args=(condition,))

    cs1.start()
    time.sleep(2)
    cs2.start()
    time.sleep(2)
    pd.start()
thread_19.py

Sincronizar hilos con objetos Condition (ii)

# -*- coding: utf-8 -*-
import threading, random, math

def funcion1(condicion):
    global lista
    print(threading.current_thread().name,
          'esperando a que se generen los números')

    condicion.acquire()
    condicion.wait()
    print('Elementos:', len(lista), threading.current_thread().name)
    print('Suma total:', math.fsum(lista),threading.current_thread().name)
    condicion.release()

def funcion2(condicion):
    global lista
    print(threading.current_thread().name,
          'generando números')

    condicion.acquire()
    for numeros in range(1, 1001):
        entero = random.randint(1,100)
        lista.append(entero)
    print('Ya hay 1000 números')
    condicion.notifyAll()
    condicion.release()

lista = []
condicion = threading.Condition()
hilo1 = threading.Thread(name='hilo1', target=funcion1,
                         args=(condicion,))                      
hilo2 = threading.Thread(name='hilo2', target=funcion2,
                         args=(condicion,))


hilo1.start()
hilo2.start()
thread_18_1.py

Sincronizar hilos con objetos Condition (iii)

# -*- coding: utf-8 -*-
import threading, random, math

def funcion1(condicion):
    global lista
    print(threading.current_thread().name,
          'esperando a que se generen los números')
    with condicion:
        condicion.wait()
        print('Elementos:', len(lista), threading.current_thread().name)
        print('Suma total:', math.fsum(lista),threading.current_thread().name)

def funcion2(condicion):
    global lista
    print(threading.current_thread().name,
          'generando números')
    with condicion:
        for numeros in range(1, 1001):
            entero = random.randint(1,100)
            lista.append(entero)
        print('Ya hay 1000 números')
        condicion.notifyAll()

lista = []
condicion = threading.Condition()
hilo1 = threading.Thread(name='hilo1', target=funcion1,
                         args=(condicion,))                      
hilo2 = threading.Thread(name='hilo2', target=funcion2,
                         args=(condicion,))


hilo1.start()
hilo2.start()
thread_18.py

Sincronizar hilos con objetos Condition (iv)

#!/usr/bin/python
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
thread_20.py

Sincronizar hilos con objetos Semaphore

  • Un objeto Semaphore es un instrumento de bloqueo que utiliza un contador interno para controlar el número de hilos que pueden acceder de forma concurrente a una parte del código.
  • Si el número de hilos que intentan acceder supera, en un momento dado, al valor establecido se producirá un bloqueo que será liberado en la medida que los hilos no bloqueados vayan completando las operaciones previstas. 

Sincronizar hilos con objetos Semaphore

  • Realmente actúa como un semáforo en la entrada de un aparcamiento público:
    • dejando pasar vehículos mientras existen plazas disponibles
    • cerrando el acceso hasta que no quede libre al menos una plaza.

Sincronizar hilos con objetos Semaphore

import threading
import time

def descargando(semaforo):
    global activas
    nombre = threading.current_thread().getName()
    print('Esperando para descargar:', nombre)
    with semaforo:
        activas.append(nombre)
        print('Descargas activas', activas)
        print('...Descargando...', nombre)
        time.sleep(0.1)
        activas.remove(nombre)
        print('Descarga finalizada', nombre)

NUM_DESCARGAS_SIM = 3
activas = []
semaforo = threading.Semaphore(NUM_DESCARGAS_SIM)
for indice in range(1,6):
    hilo = threading.Thread(target=descargando,
                            name='D' + str(indice),
                            args=(semaforo,),)
    hilo.start()
thread_22.py

Sincronizar hilos con objetos Barrier

  • Los objetos barrera (Barrier) son otro mecanismo de sincronización de hilos.
  • Como su propio nombre sugiere actúan como una verdadera barrera que mantiene los hilos bloqueados en un punto del programa hasta que todos hayan alcanzado ese punto.

 

Sincronizar hilos con objetos Barrier

import threading, random, math

def funcion1(barrera):
    nom_hilo = threading.current_thread().name 
    print(nom_hilo, 
          'Esperando con', 
          barrera.n_waiting, 
          'hilos más')
    
    numero = random.randint(1,10)
    ident = barrera.wait()
    print(nom_hilo, 
          'Ejecutando después de la espera',
          ident)
    print('factorial de',
          numero,
          'es',
          math.factorial(numero),nom_hilo)

NUM_HILOS = 5
barrera = threading.Barrier(NUM_HILOS)
hilos = [threading.Thread(name='hilo-%s' % i, 
                          target=funcion1, 
                          args=(barrera,),
                          ) for i in range(NUM_HILOS)]

for hilo in hilos:
    print(hilo.name, 'Comenzando ejecución')
    hilo.start()
thread_21.py

Sincronizar hilos con objetos Barrier

  • Existe la posibilidad de enviar un aviso de cancelación a todos los hilos que esperan con el método abort() del objeto Barrier.
  • Esta acción genera una excepción de tipo threading.BrokenBarrierError que se debe capturar y tratar convenientemente:
try:
 ident = barrera.wait()
except threading.BrokenBarrierError:
 print(nom_hilo, 'Cancelando')
else:
 print('Ejecutando después de la espera', ident)

Recursos

  • http://www.laurentluce.com/posts/python-threads-synchronization-locks-rlocks-semaphores-conditions-events-and-queues/comment-page-1/
  • https://pythonr2.wordpress.com/2008/09/01/sincronizacion-de-hilos-en-python/
  • http://luis-sierra-programacion-pd.blogspot.cl/2015/09/que-son-los-threads-para-que-una.html
  • http://python-para-impacientes.blogspot.cl/2016/12/threading-programacion-con-hilos-y-ii.html
  • http://mundogeek.net/archivos/2008/04/18/threads-en-python/

UNAB: LP12

By Miguel Cantillana

UNAB: LP12

Clase 12

  • 615