Administración de procesos: Primitivas de...

95
¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema d Administración de procesos: Primitivas de sincronización Gunnar Wolf Facultad de Ingeniería, UNAM 2013-02-13 — 2013-02-18

Transcript of Administración de procesos: Primitivas de...

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Administración de procesos: Primitivas desincronización

Gunnar Wolf

Facultad de Ingeniería, UNAM

2013-02-13 — 2013-02-18

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Índice

1 ¿Qué queremos evitar?

2 Primitivas de sincronización

3 Patrones basados en semáforos

4 Problemas clásicos

5 El problema de inicio

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Concurrencia

No tenemos que preocuparnos cuando todos los datos quemaneja un hilo son localesAl utilizar variables globales o recursos externos, debemosrecordar que el planificador puede interrumpir el flujo encualquier momentoNo tenemos garantía del ordenamiento que obtendremos

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Los problemas de la concurrencia (1)

1 class EjemploHilos2 def initialize3 @x = 04 end5 def f16 sleep 0.17 print ’+’8 @x += 39 end

1 def f22 sleep 0.13 print ’*’4 @x *= 25 end6 def run7 t1 = Thread.new {f1}8 t2 = Thread.new {f2}9 sleep 0.1

10 print ’%d ’ % @x11 end12 end

1 >> e = EjemploHilos.new;10.times{e.run}2 0 *+3 *+9 *+21 +*48 *+99 +*204 *+411 +*828 *+16593

4 >> e = EjemploHilos.new;10.times{e.run}5 +0 *+6 *+*18 42 +*+90 **186 +375 +**756 ++1515 *3036

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Los problemas de la concurrencia (2)

No son dos hilos compitiendo por el acceso a la variableSon tresEl jefe también entra en la competencia a la hora deimprimir

A veces, el órden de la ejecución es (¿parece ser?) (@x*2) + 3, a veces (@x + 3) * 2

A veces la impresión ocurre en otro órden: +**756 o++1515

Esto porque tenemos una condición de carrera en elacceso a la variable compartida

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Condición de carrera (Race condition)

Error de programaciónImplica a dos procesos (o hilos)Fallan al comunicarse su estado mutuoLleva a resultados inconsistentes

Problema muy comúnDifícil de depurar

Ocurre por no considerar la no atomicidad de unaoperaciónCategoría importante de fallos de seguridad

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Operación atómica

Operación que tenemos la garantía que se ejecutará o nocomo una sóla unidad de ejecuciónNo implica que el sistema no le retirará el flujo deejecución

El efecto de que se le retire el flujo no llevará acomportamiento inconsistente.Requiere sincronización explícita entre los procesos que larealicen

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Sección crítica

Es el área de código que:Realiza el acceso (¿modificación? ¿lectura?) de datoscompartidosRequiere ser protegida de accesos simultáneosDicha protección tiene que ser implementada siempre, ymanualmente por el programador

Identificarlas requiere inteligenciaDebe ser protegida empleando mecanismos atómicos

Si no, el problema podría aminorarse — Pero noprevenirse¡Cuidado con los accesos casi-simultáneos!

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Sección crítica

Figura: Sincronización al intentar ejecutar concurrentemente unasección crítica (imagen: Prof. Samuel Oporto Díaz)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Bloqueo mutuo

Algunos autores lo presentan como interbloqueo.En inglés, deadlock

Dos o más procesos poseen determinados recursosCada uno de ellos queda detenido esperando a alguno delos que tiene otro

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Bloqueo mutuo

Figura: Esquema clásico de un bloqueo mutuo simple: Los procesosA y B esperan mutuamente para el acceso a las unidades 1 y 2.

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Bloqueo mutuo

El sistema operativo puede seguir procesandonormalmente

Pero ninguno de los procesos involucrados puede avanzar¿Única salida? Que el administrador del sistemainterrumpa a alguno de los procesos

. . . Implica probable pérdida de información

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Inanición

En inglés, resource starvation

Situación en que uno o más procesos están atravesandoexitosamente una sección crítica

Pero el flujo no permite que otro proceso, posiblementede otra clase, entre a dicha sección

El sistema continúa siendo productivo, pero uno de losrecursos puede estar detenido por un tiempoarbitrariamente largo.

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Primer acercamiento: Reservas de autobús

¡Inseguro! ¿Qué hizo el programador bien? ¿qué hizo mal?

1 my ($proximo_asiento :shared, $capacidad :shared, $bloq:shared);

2 $capacidad = 40;3 sub asigna_asiento {4 while ($bloq) { sleep 0.1; }5 $bloq = 1;6 if ($proximo_asiento < $capacidad) {7 $asignado = $proximo_asiento;8 $proximo_asiento += 1;9 print "Asiento asignado: $asignado\n";10 } else {11 print "No hay asientos disponibles\n";12 return 1;13 }14 $bloq = 0;15 return 0;16 }

Tip: Sección crítica entre las líneas 6 y 8 (¿o hasta 14?)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

¿Por qué es inseguro el ejemplo anterior?

Líneas 4 y 5:

Espera activa (spinlock): Desperdicio de recursosAunque esta espera activa lleva dentro un sleep, siguesiendo espera activa.Eso hace que el código sea poco considerado — No quesea inseguro

¿Quién protege a $bloq de modificaciones no-atómicas?

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Las secciones críticas deben protegerse a otro nivel

Las primitivas que empleemos para sincronización debenser atómicasLa única forma de asegurar su atomicidad esimplementándolas a un nivel más bajo que el del códigoque deben proteger

(Al menos) el proceso debe implementar la protecciónentre hilos(Al menos) el sistema operativo debe implementar laprotección entre procesos

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Índice

1 ¿Qué queremos evitar?

2 Primitivas de sincronización

3 Patrones basados en semáforos

4 Problemas clásicos

5 El problema de inicio

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Requisitos para las primitivas

Implementadas a un nivel más bajo que el código queprotegen

Desde el sistema operativoDesde bibliotecas de sistemaDesde la máquina virtual (p.ej. JVM)

¡No las implementes tú mismo!Parecen conceptos simples. . . Pero no lo sonUtilicemos el conocimiento acumulado de medio siglo

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Mutex

Contracción de Mutual Exclusion, exclusión mutuaUn mecanismo que asegura que la región protegida delcódigo se ejecutará como si fuera atómica

No garantiza que el planificador no interrumpa — Esorompería el multiprocesamiento preventivo.Requiere que cada hilo o proceso implemente (¡yrespete!) al mutex

Mantiene en espera a los procesos adicionales que quieranemplearlo

Sin garantizar ordenamiento

Ejemplo: La llave del baño en un entorno de oficinamediana

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Semáforos

Propuestos por Edsger Djikstra (1965)Estructuras de datos simples para la sincronización y(muy limitada) comunicación entre procesos

¡Increíblemente versátiles para lo limitado de su interfaz!

Se han publicado muchos patrones basados en su interfaz,modelando interacciones muy complejas

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Las tres operaciones de los semáforos

Inicializar Puede inicializarse a cualquier valor entero. Unavez inicializado, el valor ya no puede ser leído.

Decrementar Disminuye en 1 el valor del semáforo. Si elresultado es negativo, el hilo se bloquea y nopuede continuar hasta que otro hilo incremente alsemáforo.Puede denominarse wait, down, acquire, P(proberen te verlagen, intentar decrementar)

Incrementar Incrementa en 1 el valor del semáforo. Si hayhilos esperando, uno de ellos es despertado.Puede denominarse signal, up, release,post o V (verhogen, incrementar).

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Los semáforos en C (POSIX pthreads)

1 int sem_init(sem_t *sem, int pshared, unsigned int value);2 int sem_post(sem_t *sem);3 int sem_wait(sem_t *sem);4 int sem_trywait(sem_t *sem);

pshared indica si se compartirá entre procesos o sóloentre hilos (por optimización de estructuras)sem_trywait extiende la interfaz de Djikstra: Verificasi el semáforo puede ser decrementado, pero en vez debloquearse, regresa al invocante un error

El proceso debe tener la lógica para no proceder a lasección crítica

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Variables Condicionales

Una extensión sobre el comportamiento de un mutexpermitiéndole una mayor “inteligencia”Operan ligadas a un mutex (candado)Implementa las siguientes operaciones:wait() Libera el candado y se bloquea hasta recibir

una notificación. Una vez despertado,re-adquiere el candado.

timedwait(timeout) Como wait(), pero sedespierta (regresando error) pasado eltiempo indicado si no recibió notificación.

signal() Despierta a un hilo esperando a estacondición (si lo hay). No libera al candado.

broadcast Notifica a todos los hilos que esténesperando a esta condición

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Variables Condicionales en C (POSIX pthreads)

1 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;2 int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t

*cond_attr);3 int pthread_cond_signal(pthread_cond_t *cond);4 int pthread_cond_broadcast(pthread_cond_t *cond);5 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t

*mutex);6 int pthread_cond_timedwait(pthread_cond_t *cond,

pthread_mutex_t *mutex, const struct timespec *abstime);7 int pthread_cond_destroy(pthread_cond_t *cond);

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Ejemplo con variables condicionales

1 int x,y;2 pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;3 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;4 /* (...) */5 /* Un hilo espera hasta que x sea mayor que y */6 pthread_mutex_lock(&mut);7 while (x <= y) {8 pthread_cond_wait(&cond, &mut);9 }10 /* (Realiza el trabajo...) */11 pthread_mutex_unlock(&mut);12 /* (...) */13 /* Cuando otro hilo modifica a x o y, notifica */14 /* a todos los hilos que est n esperando */15 pthread_mutex_lock(&mut);16 x = x + 1;17 if (x > y) pthread_cond_broadcast(&cond);18 pthread_mutex_unlock(&mut);

http://www.sourceware.org/pthreads-win32/manual/pthread_cond_init.html

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Ejemplo de espera limitada con VCs

1 struct timeval now;2 struct timespec timeout;3 int retcode;4 pthread_mutex_lock(&mut);5 gettimeofday(&now);6 timeout.tv_sec = now.tv_sec + 5;7 timeout.tv_nsec = now.tv_usec * 1000;8 retcode = 0;9 while (x <= y && retcode != ETIMEDOUT) {10 retcode = pthread_cond_timedwait(&cond, &mut, &timeout);11 }12 if (retcode == ETIMEDOUT) {13 /* Expirado el tiempo estipulado - Falla. */14 } else {15 /* Trabaja con x y y */16 }17 pthread_mutex_unlock(&mut);

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Problemática con mutexes, semáforos y VCs

No sólo hay que encontrar el mecanismo correcto paraproteger nuestras secciones críticas

hay que implementarlo correctamenteLa semántica de paso de mensajes por esta vía puede serconfusa

Un encapsulamiento más claro puede reducir problemasPuede haber procesos que compitan por recursos deforma hostil

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Competencia hostil por recursos

Qué pasa si en vez de esto:

1 sem_wait(semaforo);2 seccion_critica();3 sem_post(semaforo);

Tenemos esto:

1 while (sem_trywait(semaforo) != 0) {}2 seccion_critica();3 sem_post(semaforo);

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

La estupidez humana puede ser infinita

1 /* Mecanismo de protecci n: Crucemos los dedos... */2 /* A fin de cuentas, corremos con baja frecuencia! */3 seccion_critica();

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Monitores

Estructuras abstractas (ADTs u objetos) provistas por ellenguaje o entorno de desarrolloEncapsulan tanto a los datos como a las funciones que lospueden manipularImpiden el acceso directo a las funciones potencialmentepeligrosasExponen una serie de métodos públicos

Y pueden implementar métodos privados

Al no presentar una interfaz que puedan subvertir,aseguran que todo el código que asegura el accesoconcurrente seguro es empleadoPueden ser presentados como bibliotecas

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Visión esquemática de los monitores

Figura: Vista esquemática de un monitor. No es ya un conjunto deprocedimientos aislados, sino que una abstracción que permiterealizar únicamente las operaciones públicas sobre datosencapsulados. (Silberschatz, p.239)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Ejemplo: Sincronización en Java

Java facilita que una clase estándar se convierta en un monitorcomo una propiedad de la declaración de método, y loimplementa directamente en la JVM. (Silberschatz):

1 public class SimpleClass {2 // . . .3 public synchronized void safeMethod() {4 /* Implementation of safeMethod() */5 // . . .6 }7 }

La JVM implementa:Mutexes a través de la declaración synchronizedvariables de condiciónUna semántica parecida (¡no idéntica!) a la de semáforoscon var.wait() y var.signal()

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Soluciones en hardware

Decimos una y otra vez que la concurrencia está aquípara quedarseEl hardware especializado para cualquier cosa(interrupciones, MMU, punto flotante, etc.) es siemprecaro, hasta que baja de precio¿No podría el hardware ayudarnos a implementaroperaciones atómicas?

Veamos algunas estrategias

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Inhabilitación de interrupciones

Efectivamente evita que las secciones críticas seaninterrumpidas, pero. . .

Inútil cuando hay multiprocesamiento realA menos que detenga también la ejecución en los demásCPUs

Matar moscas a cañonazosInhabilita el multiproceso preventivo

Demasiado peligroso

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Instrucciones atómicas: test_and_set (1)

Siguiendo una implementación en hardware correspondiente a:1 boolean test_and_set(int i) {2 if (i == 0) {3 i = 1;4 return true;5 } else return false;6 }7 void free_lock(int i) {8 if (i == 1) i = 0;9 }

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Instrucciones atómicas: test_and_set (2)

Bastaría con:1 enter_region:2 tsl reg, flag ; Test and Set Lock; ’flag’ es la variable3 ; compartida, es cargada al registro ’reg’4 ; y, atomicamente, convertida en 1.5 cmp reg, #0 ; Era la bandera igual a 0 al entrar?6 jnz enter_region ; En caso de que no fuera 0 al ejecutar7 ; tsl, vuelve a ’enter_region’8 ret ; Termina la rutina. ’flag’ era cero al9 ; entrar. ’tsl’ fue exitoso y ’flag’ queda10 ; no-cero. Tenemos acceso exclusivo al11 ; recurso protegido.12 leave_region:13 move flag, #0 ; Guarda 0 en flag, liberando el recurso14 ret ; Regresa al invocante.

¿Qué problema le vemos?

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Problemas con test_and_set

Espera activaSe utiliza sólo en código no interrumpible (p.ej. gestor deinterrupciones en el núcleo)

Código no portableImposible de implementar en arquitecturas RISC limpias

Doble acceso a memoria en una sóla instrucción. . .Muy caro de implementar en arquitecturas CISC

Susceptible a problemas de coherencia de cache

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Memoria transaccional

Idea base: Semántica de bases de datos en el acceso amemoriaPermite agrupar varias operaciones en una sólatransacción

Una vez terminada, confirmar (commit) todos loscambiosO, en caso de error, rechazarlos (rollback)

Si algún otro proceso modifica alguna de las localidadesen cuestión, el proceso se rechazaToda lectura de memoria antes de confirmar entrega losdatos previos

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Memoria transaccional: Ejemplo de semántica

1 do {2 begin_transaction();3 var1 = var2 * var3;4 var3 = var2 - var1;5 var2 = var1 / var2;6 } while (! commit_transaction());

Ejemplo poco eficiente, elegido meramente por claridad:Efectúa múltiples cálculos dentro de una espera activa efectiva

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Memoria transaccional en software (STM)

Implementaciones como la mencionada disponibles envarios lenguajesComputacionalmente caras

Invariablemente mucho más lentasY menos eficientes en espacio. . . Pero en hardware tampoco serían mucho más baratas— y sí tendrían restricciones (en tamaño o cantidad detransacciones simultáneas)

Puede llevar a inconsistencias si implican cualquierestructura fuera del control de la transacción (archivos,dispositivos, IPC)Construcción poderosa (¡y cómoda!)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Índice

1 ¿Qué queremos evitar?

2 Primitivas de sincronización

3 Patrones basados en semáforos

4 Problemas clásicos

5 El problema de inicio

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Señalizar

Un hilo debe informar a otro que cierta condición estácumplidaEjemplo: Un hilo prepara una conexión en red mientras elotro prepara los datos a enviar

No podemos arriesgarnos a comenzar la transmisiónhasta que la conexión esté lista

1 # Antes de lanzar los hilos2 senal = Semaphore(0)3

4 def prepara_conexion():5 crea_conexion()6 senal.release()

1 def envia_datos():2 calcula_datos()3 senal.acquire()4 envia_por_red()

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Rendezvous

Nombre tomado del francés para preséntense (utilizadoampliamente en inglés para tiempo y lugar de encuentro)Dos hilos deben esperarse mutuamente en cierto puntopara continuar en conjunto

Empleamos dos semáforosPor ejemplo, en un GUI:

Un hilo prepara la interfaz gráfica y actualiza sus eventosOtro hilo efectúa cálculos para mostrarQueremos mostrar la simulación desde el principio, nodebe iniciar el cálculo antes de que haya una interfazmostradaNo queremos que la interfaz se presente en blanco

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Rendezvous

1 guiListo = Semaphore(0)2 calculoListo = Semaphore(0)3 threading.Thread(target=gui,

args=[]).start()4 threading.Thread(target=calculo,

args=[]).start()

1 def calculo():2 inicializa_datos()3 calculoListo.release()4 guiListo.acquire()5 procesa_calculo()6

7 def gui():8 inicializa_gui()9 guiListo.release()

10 calculoListo.acquire()11 recibe_eventos()

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Mutex

Un mutex puede implementarse con un semáforo inicializado a1:

1 mutex = Semaphore(1)2 # ...Inicializamos estado y lanzamos hilos3 mutex.acquire()4 # Estamos en la region de exclusion mutua5 x = x + 16 mutex.release()7 # Continua la ejecucion paralela

Varios hilos pueden pasar por este código, tranquilos deque la región crítica será accesada por sólo uno a la vezEl mismo mutex puede proteger a diferentes seccionescríticas (p.ej. distintos puntos donde se usa el mismorecurso)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Multiplex

Un mutex que permita a no más de cierta cantidad dehilos empleando determinado recursoPara implementar un multiplex, basta inicializar elsemáforo de nuestro mutex a un valor superior:

1 multiplex = Semaphore(5)2 # ...Inicializamos estado y lanzamos hilos3 multiplex.acquire()4 # Estamos en la region de exclusion mutua5 # (con hasta cinco hilos concurrentes)6 x = x + 17 multiplex.release()8 # Continua la ejecucion paralela

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Torniquete

Garantiza que un grupo de hilos o procesos pasan por unpunto determinado de uno en unoAyuda a controlar contención, es empleado como parte deconstrucciones posteriores

1 torniquete = Semaphore(0)2 # (...)3 if alguna_condicion():4 torniquete.release()5 # (...)6 torniquete.acquire()7 torniquete.release()

Esperamos primero a una señalización que permita quelos procesos comiencen a fluirLa sucesión rápida acquire() / release() permiteque los procesos fluyan uno a uno

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Apagador

Principalmente emplados en situación de exclusióncategórica

Categorías de procesos, no procesos individuales, quedeben excluirse mutuamente de secciones críticas

Metáfora empleada: La zona de exclusión mutua es uncuarto, y los procesos que quieren entrar deben verificar siestá prendida la luzImplementación ejemplo a continuación (problema delectores-escritores)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Barrera

Generalización de rendezvous para manejar a varios hilos(no sólo dos)El rol de cada uno de estos hilos puede ser el mismo,puede ser distintoRequiere de una variable adicional para mantener registrode su estado

Esta variable adicional es compartida entre los hilos, ydebe ser protegida por un mutex

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Barrera: Código ejemplo

1 require random2 # n = Numero de hilos3 n = random.randint(1,10)4 cuenta = 05 mutex = Semaphore(1)6 barrera = Semaphore(0)

1 inicializa_estado()2

3 mutex.acquire()4 count = count + 15 mutex.release()6

7 if count == n:8 barrera.release()9

10 barrera.acquire()11 barrera.release()12

13 procesamiento()

Todos los hilos se inicializan por separado(inicializa_estado())Ningún hilo inicia hasta que todos estén listosPasar la barrera en este caso equivale a habilitar untorniquete

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Barreras: Implementación en pthreads

Las barreras son una construcción tan común que lasencontramos “prefabricadas”Definición en los hilos POSIX (pthreads):

1 int pthread_barrier_init(pthread_barrier_t *barrier,2 const pthread_barrierattr_t *restrict attr,3 unsigned count);4 int pthread_barrier_wait(pthread_barrier_t *barrier);5 int pthread_barrier_destroy(pthread_barrier_t *barrier);

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cola

Tenemos que asegurarnos que procesos de dos distintascategorías procedan siempre en paresPatrón conocido también como baile de salón:

Para que una pareja baile, tiene que haber un líder y unseguidorCuando llega al salón un líder, revisa si hay algúnseguidor esperando

Si lo hay, bailanSi no, espera a que llegue uno

El seguidor procede de forma análoga.

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cola1 colaLideres = Semaphore(0)2 colaSeguidores = Semaphore(0)3 # (...)4 def lider():5 colaSeguidores.release()6 colaLideres.acquire()7 baila()8 def seguidor():9 colaLideres.release()10 colaSeguidores.acquire()11 baila()

Nuevamente, estamos viendo un rendezvousPero es entre dos categorías, no entre dos hilosespecíficos

El patrón puede refinarse mucho, esta es laimplementación básica

Asegurarse que sólo una pareja baile a la vezAsegurarse que bailendo en el órden en que llegaron

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Índice

1 ¿Qué queremos evitar?

2 Primitivas de sincronización

3 Patrones basados en semáforos

4 Problemas clásicos

5 El problema de inicio

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

¿De qué se tratan estos problemas clásicos?

Manera fácil de recordar para hablar de situacionescomunes en la vida realForma de demostrar las ventajas/desventajas de unaconstrucción de sincronización frente a otrasAmpliamente utilizados en la literatura de la materiaAyudan a comprender la complejidad del manejo de lospatrones, aparentemente simples

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Problema productor-consumidor: Planteamiento

División de tareas tipo línea de ensambladoUn grupo de procesos va produciendo ciertas estructurasOtro grupo va consumiéndolasEmplean un buffer de acceso compartido paracomunicarse dichas estructuras

Agregar o retirar un elemento del buffer debe hacerse deforma atómicaSi un consumidor está listo y el buffer está vacío, debebloquearse (¡no espera activa!)

Refinamientos posterioresImplementación con un buffer no-infinito (¿buffercircular?):

Vida realCola de trabajos para impresiónPipes (tuberías) entre procesos

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Productor-consumidor: Implementación ingenua

1 import threading2 buffer = []3 threading.Thread(target=productor,args=[]).start()4 threading.Thread(target=consumidor,args=[]).start()5

6 def productor():7 while True:8 event = genera_evento()9 buffer.append(event)10

11 def consumidor():12 while True:13 event = buffer.pop()14 procesa(event)

¿Qué problema vemos?¿Qué estructuras neceistan protección?

(¿Qué estructuras no?)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Productor-consumidor: Estructuras a emplear

Vamos a emplear dos semáforos:Un mutex sencillo (mutex)Un semáforo (elementos) representando el estado delsistemaelementos > 0 Cuántos eventos tenemos pendientes

por procesarelementos < 0 Cuántos consumidores están listos y

esperando un evento

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Productor-consumidor: Implementación

1 import threading2 mutex = threading.Semaphore(1)3 elementos = threading.Semaphore(0)4 buffer = []5 threading.Thread(target=productor, args=[]).start()6 threading.Thread(target=consumidor, args=[]).start()

1 def productor():2 while True:3 event = genera_evento()4 mutex.acquire()5 buffer.append(event)6 mutex.release()7 elementos.release()

1 def consumidor():2 while True:3 elementos.acquire()4 mutex.acquire()5 event = buffer.pop()6 mutex.release()7 event.process()

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Problema lectores-escritores: Planteamiento

Una estructura de datos puede ser accesadasimultáneamente por muchos procesos lectoresSi un proceso requiere modificarla, debe asegurar que:

Ningún proceso lector esté empleándolaNingún otro proceso escritor esté empleándolaLos escritores deben tener acceso exclusivo a la seccióncrítica

Refinamiento: Debemos evitar que un influjo constante delectores nos deje en situación de inanición

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Lectores-escritores: Primer acercamiento

El problema es de exclusión mutua categóricaEmpleamos un patrón apagador

Los escritores entran al cuarto sólo con la luz apagadaMutex para el indicador del número de lectores

1 import threading2 lectores = 03 mutex = threading.Semaphore(1)4 cuarto_vacio =

threading.Semaphore(1)5

6 def escritor():7 cuarto_vacio.acquire()8 escribe()9 cuarto_vacio.release()

1 def lector():2 mutex.acquire()3 lectores = lectores + 14 if lectores == 1:5 cuarto_vacio.acquire()6 mutex.release()7

8 lee()9

10 mutex.acquire()11 lectores = lectores - 112 if lectores == 0:13 cuarto_vacio.release()14 mutex.release()

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Lectores-escritores: Notas

La misma estructura puede usarse siguiendo diferentespatrones

escritor() usa cuarto_vacio como un mutex,lector() lo usa como un apagadorPorque las características (requisitos) de cada categoríason distintas

Susceptible a la inaniciónSi tenemos alta concurrencia de lectores, un escritorpuede quedarse esperando para siemprePodemos agregar un torniquete evitando que lectoresadicionales se cuelen si hay un escritor esperando

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Lectores-escritores sin inanición

1 import threading2 lectores = 03 mutex = threading.Semaphore(1)4 cuarto_vacio =

threading.Semaphore(1)5 torniquete =

threading.Semaphore(1)6

7 def escritor():8 torniquete.acquire()9 cuarto_vacio.acquire()10 escribe()11 cuarto_vacio.release()12 torniquete.release()

1 def lector():2 torniquete.acquire()3 torniquete.release()4

5 mutex.acquire()6 lectores = lectores + 17 if lectores == 1:8 cuarto_vacio.acquire()9 mutex.release()

10

11 lee()12

13 mutex.acquire()14 lectores = lectores - 115 if lectores == 0:16 cuarto_vacio.release()17 mutex.release()

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

La cena de los filósofos: Planteamiento (1)

Hay cinco filósofos sentados a la mesaAl centro de la mesa hay un tazón de arrozCada filósofo tiene un plato, un palillo a la derecha, y unpalillo a la izquierdaEl palillo lo comparten con el filósofo de junto

Imagenes ilustrativas: Ted P. Baker

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

La cena de los filósofos: Planteamiento (2)

Cada filósofo sólo sabe hacer dos cosas: Pensar y comerLos filósofos piensan hasta que les da hambre

Una vez que tiene hambre, un filósofo levanta un palillo,luego levanta el otro, y comeCuando se sacia, pone en la mesa un palillo, y luego elotro

¿Qué problemas pueden presentarse?

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Bloqueo mutuo

Figura: Cuando todos los filósofos intentan levantar uno de lospalillos se produce un bloqueo mutuo

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Inanición

Figura: Una rápida sucesión de C y E lleva a la inanición de D

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Primer acercamiento

Para la resolución de este problema, representaremos a lospalillos como un arreglo de semáforos

Esto asegura que presenten la semántica de exclusiónmutua: levantar un palillo es una operación atómica

Cada filósofo sabe cuál es su ID (numérico, 0 a n;ejemplo con n = 5)

Los palillos de i son palillos[i] ypalillos[(i+1)% n]

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Primer acercamiento1 import threading2 num = 53 palillos = [threading.Semaphore(1) for i in range(num)]4 filosofos = [threading.Thread(target=filosofo,

args=[i]).start() for i in range(num)]

1 def filosofo(id):2 while True:3 piensa(id)4 levanta_palillos(id)5 come(id)6 suelta_palillos(id)7

8 def levanta_palillos(id):9 palillos[(id+1) %

num].acquire()10 print "%d - Tengo el palillo

derecho" % id11 palillos[id].acquire()12 print "%d - Tengo ambos

palillos" % id

1 def suelta_palillos(id):2 palillos[(id+1) %

num].release()3 palillos[id].release()4 print "%d - Sigamos

pensando..." % id5

6 def piensa(id):7 # (...)8 print "%d - Tengo hambre..."

% id9

10 def come(id):11 print "%d - A comer!" % id12 # (...)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Semáforos para comunicación

Sujeto a bloqueos mutuosEventualmente, terminarán todos suspendidos con elpalillo derecho en la mano

¿Ideas para evitar el bloqueo mutuo?

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Filósofos zurdos

¿Y si hacemos que los filósofos pares sean diestros y losimpares sean zurdos?

1 def levanta_palillos(id):2 if (id % 2 == 0): # Zurdo3 palillo1 = palillos[id]4 palillo2 = palillos[(id+1) % num]5 else: # Diestro6 palillo1 = palillos[(id+1) % num]7 palillo2 = palillos[id]8 palillo1.acquire()9 print "%d - Tengo el primer palillo" % id10 palillo2.acquire()11 print "%d - Tengo ambos palillos" % id

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Basta con uno

De hecho, basta con que uno de los filósofos sea zurdo paraque no haya bloqueo mutuo

1 def levanta_palillos(id):2 if id == 0: # Zurdo3 palillos[id].acquire()4 print "%d - Tengo el palillo izquierdo" % id5 palillos[(id+1) % num].acquire()6 else: # Diestro7 palillos[(id+1) % num].acquire()8 print "%d - Tengo el palillo derecho" % id9 palillos[id].acquire()10 print "%d - Tengo ambos palillos" % id

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Monitor y VCs (C)

Implementación de Ted P. Baker (Florida State University)basada en Tanenbaum

1 /* Implementacion para cinco filosofos */2 pthread_cond_t CV[NTHREADS]; /* Variable por filosofo */3 pthread_mutex_t M; /* Mutex para el monitor */4 int state[NTHREADS]; /* Estado de cada filosofo */5

6 void init () {7 int i;8 pthread_mutex_init(&M, NULL);9 for (i = 0; i < 5; i++) {10 pthread_cond_init(&CV[i], NULL);11 state[i] = PENSANDO;12 }13 }14

15 void come(int i) {16 printf("El filosofo %d esta comiendo\n", i);17 }

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Monitor y VCs (C)

1 void toma_palillos (int i) {2 pthread_mutex_lock(&M)3 state[i] = HAMBRIENTO;4 actualiza(i);5 while (state[i] == HAMBRIENTO)6 pthread_cond_wait(&CV[i], &M);7 pthread_mutex_unlock(&M);8 }9

10 void suelta_palillos (int i) {11 state[i] = PENSANDO;12 actualiza((i + 4) % 5);13 actualiza((i + 1) % 5);14 pthread_mutex_unlock(&M);15 }

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Monitor y VCs (C)

1 /* No incluimos ’actualiza’ en los encabezados (funcioninterna) */

2 int actualiza (int i) {3 if ((state[(i + 4) % 5] != COMIENDO) &&4 (state[i] == HAMBRIENTO) &&5 (state[(i + 1) % 5] != COMIENDO)) {6 state[i] = COMIENDO;7 pthread_cond_signal(&CV[i]);8 }9 return 0;10 }

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: Monitor y VCs (C)

Figura: Representación del monitor

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Cena de filósofos: ¿Y la inanición?

La inanición es un problema mucho más dificil de tratarque el bloqueo mutuoEl algoritmo presentado por Tanenbaum (1987) buscabaprevenir la inanición, pero Gingras (1990) demostró queaún éste es vulnerable a ciertos patronesGingras propone un algoritmo libre de inanición, perodemanda de estructuras adicionales que rompen elplanteamiento del problema original

Artículo de Gingras (1990): Dining philosophers revisited(descargable desde la red de la UNAM — No RIU)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Fumadores compulsivos: Planteamiento

Para fumar hacen falta tres ingredientesTabaco, papel, cerillos

Hay tres fumadores compulsivos / en cadenaCada uno tiene una cantidad ilimitada de uno de losingredientesNo cooperan, pero no se estorban: No comparten, perono acaparan

Un agente de tiempo en tiempo consigue insumos enpares

Cuando no hay nada en la mesa, puede poner una dosisde dos ingredientesEl agente no habla con los fumadores. Sólo deja losingredientes cuando uno de ellos termina de fumar.Requisito planteado: No podemos modificar la lógica delagente

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Fumadores compulsivos: Primer implementación

Estructuras comunes:

1 import random2 import threading3 ingredientes = [’tabaco’, ’papel’, ’cerillo’]4 semaforos = {}5 semaforo_agente = threading.Semaphore(1)6 for i in ingredientes:7 semaforos[i] = threading.Semaphore(0)8

9 threading.Thread(target=agente, args=[]).start()10 fumadores = [threading.Thread(target=fumador, args=[i]).start()

for i in ingredientes]11

12 def fuma(ingr):13 print ’Fumador con %s echando humo...’ % ingr

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Fumadores compulsivos: Primer implementación

1 def agente():2 while True:3 semaforo_agente.acquire()4 mis_ingr = ingredientes[:]5 mis_ingr.remove(random.choice(mis_ingr))6 for i in mis_ingr:7 print "Proveyendo %s" % i8 semaforos[i].release()9

10 def fumador(ingr):11 mis_semaf = []12 for i in semaforos.keys():13 if i != ingr:14 mis_semaf.append(semaforos[i])15 while True:16 for i in mis_semaf:17 i.acquire()18 fuma(ingr)19 semaforo_agente.release()

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Fumadores compulsivos: Bloqueo mutuo

Tenemos un semáforo por ingredientePero no podemos asegurar el ordenamientoAl aparecer un ingrediente en la mesa, cualquiera de losdos fumadores que lo requiera lo va a tomar

La mitad de las veces, el segundo ingrediente queaparezca no le serviráOtro de los fumadores tomará este segundo ingrediente¡Bloqueo!

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Fumadores compulsivos: Empleando intermediarios

No podemos modificar al agente, pero sí a los fumadoresPodemos usar intermediarios

Uno por cada ingredienteCon comunicación entre síCuando toman una decisión, despiertan al fumador encuestión

Agregamos algunas variables globales para representar alos intermediarios y la comunicación entre ellos

1 que_tengo = {}2 semaforos_interm = {}3 for i in ingredientes:4 que_tengo[i] = False5 semaforos_interm[i] = threading.Semaphore(0)6 interm_mutex = threading.Semaphore(1)7 intermediarios = [threading.Thread(target=intermediario,

args=[i]).start() for i in ingredientes]

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Fumadores compulsivos: Empleando intermediarios

El código del fumador resulta mucho más simple:

1 def fumador(ingr):2 while True:3 semaforos_interm[ingr].acquire()4 fuma(ingr)5 semaforo_agente.release()

Sólo debe esperar a que su intermediario lo despierte.Sigue siendo su responsabilidad notificar al agente para que

éste continúe.

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Fumadores compulsivos: Empleando intermediarios

¿Qué y cómo se comunican entre sí los intermediarios?

1 def intermediario(ingr):2 otros_ingr = ingredientes[:]3 otros_ingr.remove(ingr)4 while True:5 semaforos[ingr].acquire()6 interm_mutex.acquire()7 for i in otros_ingr:8 if que_tengo[i]:9 que_tengo[i] = False10 semaforos_interm[i].release()11 break12 que_tengo[i] = True13 interm_mutex.release()

Tip: Intentar analizar el flujo en paralelo de los tresintermediarios

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Índice

1 ¿Qué queremos evitar?

2 Primitivas de sincronización

3 Patrones basados en semáforos

4 Problemas clásicos

5 El problema de inicio

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Volviendo al problema de la concurrencia

A modo de corolario, intentemos resolver el problema inicial. . .

1 class EjemploHilos2 def initialize3 @x = 04 end5 def f16 sleep 0.17 print ’+’8 @x += 39 end

1 def f22 sleep 0.13 print ’*’4 @x *= 25 end6 def run7 t1 = Thread.new {f1}8 t2 = Thread.new {f2}9 sleep 0.1

10 print ’%d ’ % @x11 end12 end

1 >> e = EjemploHilos.new;10.times{e.run}2 0 *+3 *+9 *+21 +*48 *+99 +*204 *+411 +*828 *+16593

4 >> e = EjemploHilos.new;10.times{e.run}5 +0 *+6 *+*18 42 +*+90 **186 +375 +**756 ++1515 *3036

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Devolviendo la predictibilidad

Tenemos un programa explícitamente hecho para fallarCon muchos vicios en su código

Ilustra cómo los hilos pueden enredarse entre sí. . . ¿Cómo desenmarañar la madeja?

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

¿Bastará con proteger con mutex?

1 class EjemploHilos2 def initialize3 @x = 04 @mut = Mutex.new5 end6 def run7 t1 = Thread.new {f1}8 t2 = Thread.new {f2}9 sleep 0.110 @mut.lock11 print ’%d ’ % @x12 @mut.unlock13 end

1 def f12 sleep 0.13 @mut.lock4 print ’+’5 @x += 36 @mut.unlock7 end8 def f29 sleep 0.1

10 @mut.lock11 print ’*’12 @x *= 213 @mut.unlock14 end15 end

¿Será con esto suficiente?¿Tendremos resultados consistentes?

¿Por qué?¿Para qué intento proteger con el mutex al print en run?

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Un mutex no es suficiente

1 >> e = EjemploHilos.new;10.times{e.run}2 0 *++3 *12 *+*27 +57 +*120 *+243 +*492 +*+993 *1986 +=> 103 >> * e = EjemploHilos.new;10.times{e.run}4 0 +**6 +15 +*36 +*+78 *162 +*330 +*666 +**1338 +2679 => 105 >> *

Nuestro problema no sólo venía del acceso concurrente a@xSino que al ordenamiento relativo

No importa que entren a la vez(a + b) * c ! a + (b * c)=

¿Cómo podemos asegurar una invocación ordenada sinperder el paralelismo?

Las tres funciones (f1, f2 y run) incluyen unsleep(0.1)Que ese sleep reperesente nuestro código paralelizable

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Solución 1: Esperar a la finalización de los hilos

Del mismo modo que podemos lanzar un hilo nuevo (en Ruby,Thread.new {...}; en Python,

threading.Thread(...).start()), podemos esperara que termine, con join

1 class EjemploHilos2 def initialize3 @x = 04 end5 def run6 t1 = Thread.new {f1}7 t2 = Thread.new {f2(t1)}8 sleep 0.19 t2.join10 print ’%d ’ % @x11 end

1 def f12 sleep 0.13 print ’+’4 @x += 35 end6 def f2(t)7 sleep 0.18 t.join9 print ’*’

10 @x *= 211 end12 end

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Esperar a la finalización

1 >> e = EjemploHilos.new;10.times{e.run}2 +*6 +*18 +*42 +*90 +*186 +*378 +*762 +*1530 +*3066 +*6138 => 103 >> e = EjemploHilos.new;10.times{e.run}4 +*6 +*18 +*42 +*90 +*186 +*378 +*762 +*1530 +*3066 +*6138 => 10

Código resultante claro, aunque más rígido¿Qué tendríamos que modificar para que lamultiplicación ocurra antes de la suma?

Mayor acoplamiento entre las tres funcionesf2 debe recibir una referencia al hilo 1 (t)Cada función debe comprender su rol en la línea deensamblaje

¿Aprovecho efectivamente el paralelismo?¿Se ejecutan a la vez las secciones no críticas? (lossleep 0.1)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Modificando el flujo

1 def run2 t1 = Thread.new {f1}3 t2 = Thread.new {f2(t1)}4 sleep 0.15 t2.join6 print ’%d ’ % @x7 end8 def f19 sleep 0.1

10 print ’+’11 @x += 312 end13 def f2(t)14 sleep 0.115 t.join16 print ’*’17 @x *= 218 end

->

1 def run2 t1 = Thread.new {f2}3 t2 = Thread.new {f1(t1)}4 sleep 0.15 t2.join6 print ’%d ’ % @x7 end8 def f1(t)9 sleep 0.110 t.join11 print ’+’12 @x += 313 end14 def f215 sleep 0.116 print ’*’17 @x *= 218 end

1 e = EjemploHilos.new;10.times{e.run}2 *+3 *+9 *+21 *+45 *+93 *+189 *+381 *+765 *+1533 *+3069 => 10

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Solución 2: Variables de condición

1 class EjemploHilos2 require ’thread’3 def initialize4 @x = 0; @estado = 05 @mut = Mutex.new6 @cv = ConditionVariable.new7 end8 def run9 @estado = 010 t1 = Thread.new {f1}11 t2 = Thread.new {f2}12 sleep 0.113 @mut.lock14 @cv.wait(@mut) while

(@estado != 2)15 puts ’%d ’ % @x16 @mut.unlock17 end

1 def f12 sleep 0.13 @mut.lock4 @cv.wait(@mut) while

(@estado != 0)5 @x += 3; @estado += 16 @cv.broadcast(@mut)7 @mut.unlock8 end9 def f2

10 sleep 0.111 @mut.lock12 @cv.wait(@mut) while

(@estado != 1)13 @x *= 2; @estado += 114 @cv.broadcast(@mut)15 @mut.unlock16 end17 end

Ojo: No funcional (el intérprete detecta un bloqueo mutuo)

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

Sincronización a través de CV

Código resultante más complejo (más elementos aconsiderar), aunque más flexible

Ejemplo de complejidad: No funcionó tras un tiemporazonable :-PPodría mejorarse con un poco de azucar sintáctico

Menor acoplamiento entre funcionesOrdenamiento determinado por @estadoPodría configurarse en un sólo punto, p.ej:

1 def initialize2 (...) @orden = {:f1 => 0, :f2 => 1, :run => 2}3 end4 def f15 (...) @cv.wait(@mut) while (@estado != @orden[:f1])6 end

¿Qué queremos evitar? Primitivas de sincronización Patrones basados en semáforos Problemas clásicos El problema de inicio

¿Y cómo es que el intérprete detecta. . . ?

Habrán notado que el comentario al código basado en CVmenciona que el intérprete detecta un bloqueo mutuoIndica, sin duda, un error del programador

1 >> EjemploHilos.new.run2 fatal: deadlock detected3 from /usr/lib/ruby/1.9.1/thread.rb:71:in ’sleep’4 from /usr/lib/ruby/1.9.1/thread.rb:71:in ’wait’5 from (irb):15:in ’run’6 from (irb):427 from /usr/bin/irb:12:in ’<main>’

Pero. . . ¿Cómo se dio cuenta?¿Cómo puede el intérprete prevenir este bloqueo ynotificar al programador?

¡Antes de la primer iteración!