Post on 16-Oct-2018
¿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!