exclusión mutua y semáforos

27
Exclusión Mutua, Semáforos, Monitores

Upload: sammy-manuel-dominguez

Post on 26-Jun-2015

789 views

Category:

Documents


1 download

TRANSCRIPT

Page 1: Exclusión Mutua y Semáforos

Exclusión Mutua, Semáforos, Monitores

Page 2: Exclusión Mutua y Semáforos

Hoy

• Soporte de hardware para la sincronización• Abstracciones de sincronización de alto nivel

– Semáforos, monitores, y variables de condición

• Paradigmas de programación de programas concurrentes

Page 3: Exclusión Mutua y Semáforos

Problema general

• La abstracción de los hilos es buena:– Mantienen el modelo de ejecución secuencial – Permiten una implementación simple de

paraleleismo• Desafortunadamente, resuslta complicado

accesar estados compartidos entre los hilos – Ejemplo “Demasiada Leche”– Implementar un programa concurrente usando

solo carga y almacenamiento atómicos a memoria es unmétodo truculento y propenso a errores

• Ahora veremos como implementar operaciones de mas alto nivel encima de las operaciones atómicas provistas por el hardware– Creación herramientas para sincronización– Exploración de paradigmas de programación

Page 4: Exclusión Mutua y Semáforos

A donde vamos con la sincronización?

• Es necesario implementar primitivas de sincronización de más alto nivel– Es muy difícil si las únicas primitivas atómicas

son cargar de memoria y almacenar en memoria (load and store)

– Es necesario tener primitivas útiles a nivel de usuario

Load/Store Disable Ints Test&Set Comp&Swap

Locks Semáforos Monitores Send/Receive

Programas compartidos

Hardware

API de mas alto

nivel

Programas

Page 5: Exclusión Mutua y Semáforos

Como implementar Locks?

• Lock: evita que alguien haga algo– Se usa un lock antes de entrar a una sección crítica y

antes de usar datos compartidos– Se remueve el lock al salir, y luego de acceder a los

datos– Si se encuentra un lock se debe esperar

» Toda sincronización implica esperar• Carga/almacenamiento atómicas: solición Leche #3

– Muy compleja y propensa a errores• Instrucción de Lock en Hardware

– Buena idea?– Complejo?

» Cada nueva función hace al hardware mas complejo y lento

– Como se pondría un hilo a dormir desde el hardware?» Como sería el interface entre el hardware y el

programador?

Page 6: Exclusión Mutua y Semáforos

• Como construir operaciones atómicas de multiples instruciones?– Recordar: El despachador toma el control de dos maneras.

» Interna: El hilo hace algo para renunciar al CPU» Externa: Las interrupciones hacen al despachador tomar control

– En un ambiente uniprocesador, se evita el switcheo de hilo:» Evitando eventos internos (difícil con memoria virtual, swap)» Evitando los eventos externos al deshabilitar la interrupciones

• Veamos una implementacion ingenua de locks:LockAcquire { disable Ints; }LockRelease { enable Ints; }

• Problemas con este enfoque:– No se puede permitir al usuario hacer esto! Consideremos

esto:LockAcquire();While(TRUE) {;}

– Sistemas de Tiempo Real — no hay garantías de tiempo! » Las secciones críticas pueden ser arbitrariamente largas

– Que pasa con las E/S y otros eventos importantes?» “Peligro alerta roja. Ayuda!”

Implementación ingenua usando habilitar/deshabilitar interrupciones

Page 7: Exclusión Mutua y Semáforos

Una mejor implementación de Locks Deshabilitando Interrupciones

• Idea clave: mantener una variable de lock e imponer exclusión mutua solo en los accesos a esa variable

int value = FREE;

Acquire() {disable interrupts;if (value == BUSY) {

poner hilo en espera;ir a sleep();// Habilitar interrup?

} else {value = BUSY;

}enable interrupts;

}

Release() {disable interrupts;if (anyone on wait queue) {

sacar hilo de esperaPoner en cola ready;

} else {value = FREE;

}enable interrupts;

}

Page 8: Exclusión Mutua y Semáforos

Implementacion de nuevo Lock: Discusion

• Por que tenemos que deshabilitar las interrupciones?– Evitar interrupciones entre el chequeo y seteo del el valor de

lock– De otra manera ambos hilos podrían pensar que tienen el lock

• Nota: a diferencia de la solución anterior, la sección crítica (dentro de Acquire()) es bien corta– El usuario del lock puede durar lo que desee en su

seción crítica sin impactar al sistema– Interrupciones críticas pueden atenderse a tiempo!

Acquire() {disable interrupts;if (value == BUSY) {

poner hilo en espera;ir a sleep();// Habilitar interrup?

} else {value = BUSY;

}enable interrupts;

}

SecciónCrítica

Page 9: Exclusión Mutua y Semáforos

Habilitación de interrupciones en “going to sleep”

• Como habilitar interrupciones cuando se va a dormir?

• Antes de colocar el hilo en la cola de espera?– Release puede verificar la cola y no encontrar el hilo

• Luego de colocar el hilo en la cola de espera?– Release pone al hilo en la lista de ready, pero el hilo

sigue pensando que debe ir a dormir– Se pierde el wakeup y se queda con el lock (deadlock!)

• Ponerlo después de el sleep(). Pero – como?

Acquire() {disable interrupts;if (value == BUSY) {

poner hilo en espera;ir a sleep();

} else {value = BUSY;

}enable interrupts;

}

Habilitar IntHabilitar IntHabilitar Int

Page 10: Exclusión Mutua y Semáforos

Como habilitar despues de ir a dormir - Sleep()?

– Es responsabilidad del próximo hilo habiltar las interrupciones

– Cuando un hilo dormido despierta, retorna a Acquire() y habilita las interruciones

Hilo A Hilo B..

deshabilitar intssleep

sleep return enable ints

. . .

disable int sleep

sleep returnenable ints

.

.

switchcontexto

switch

contexto

Page 11: Exclusión Mutua y Semáforos

Instruciones atómicas Lectura-Modificación-Escritura

• Problemas con esta solución:– No se puede permitir uso a nivel de usuario– No trabaja bien en ambiente multiprocesador

» Deshabilitar las int en todos los proc. requiere envio de mensajes y consume mucho tiempo

• Alternativa: Secuencias de instruciones atómicas– Estas instruciones leen un valor de memoria y

escriben otro valor de manera atómica– La implementación se hace a nivel de hardware

» tanto en ambiente uniprocesador (not too hard) » como en multiprocesador (requiere de protocolos

de coherencia de las memoria cache de los proc.)

– A diferencia de la técnica de deshabilitar int., esta solución funciona en ambientes uniprocesador y multiprocesador

Page 12: Exclusión Mutua y Semáforos

Ejemplo de secuencias Leer-Modificar-Escribir

• test&set (&address) { /* Mayoría de arquitecturas */result = M[address];M[address] = 1;return result;

}• swap (&address, register) { /* x86 */

temp = M[address];M[address] = register;register = temp;

}• compare&swap (&address, reg1, reg2) { /* 68000 */

if (reg1 == M[address]) {M[address] = reg2;return success;

} else {return failure;

}}

• load-linked&store conditional(&address) { /* R4000, alpha */

loop:ll r1, M[address];movi r2, 1; /*Puede hacer comp

arbitrarias*/sc r2, M[address];beqz r2, loop;

}

Page 13: Exclusión Mutua y Semáforos

Implementación de Locks con test&set

• Otra solución incompleta, pero simple:int value = 0; // LibreAcquire() {

while (test&set(value)); // while busy}Release() {

value = 0;}

• Explicación simple:– Si lock esta libre, test&set lee 0 y hace value=1, de

esta manera lockcambia a ocupado. Retorna 0, por lo que “while” termina.

– Si lock esta ocupado, test&set lee 1 y hace value=1 (no hay cambio). Retorna 1, entonces el loop “while” continúa

– Cuando hacemos value = 0, otro hilo puede tomar el lock

• Espera ocupada (Busy-Waiting): el hilo consume ciclos del reloj mientras espera (implementación deficiente)

Page 14: Exclusión Mutua y Semáforos

Problema: Espera ocupada por Lock• Lo positivo en esta solución

– La maquina puede recibir interrupciones– Se puede usar a nivel de usuario– Funciona bien en ambiente multiprocesador

• Lo negativo– Muy ineficiente la espera ocupada consume ciclos

del reloj mientras espera– El hilo en espera podría estar usando los ciclos

que necesita el hilo que tiene el lock (todos pierden!)

– Inversión de prioridad: Si el que espera tiene mayor prioridad que el que tiene el lock no hay progreso!

• El Martian Rover original sufrió un problema de inversión de prioridad

• La espera ocupada puede durar un lapso arbitrario de tiempo!– Aún si esta solución funcionara para locks no

resultaría para otras primitivas de sincronización como son los semáforos y monitores

Page 15: Exclusión Mutua y Semáforos

Mejores locks usando test&set• Se pueden construir locks sin espera ocupada

usando test&set?– No del todo , pero se puede minimizar!– Idea: solo permitir espera ocupada al verificar lock

• Nota: sleep debe asegurarse resetear variable guard– Pro que no hacerlo justo antes o después de dormir ?

Release() {// Tiempo de espera cortowhile (test&set(guard));if alguien en cola espera{

sacar hilo de esperaPoner en cola ready;

} else {value = FREE;

}guard = 0;

int guard = 0;int value = FREE;

Acquire() {// Tiempo de espera cortowhile (test&set(guard));if (value == BUSY) {

poner hilo en espera;ir a sleep() & guard = 0;

} else {value = BUSY;guard = 0;

}}

Page 16: Exclusión Mutua y Semáforos

Primitivas de mayor nivel de los Locks

• Cual es la meta:– Cual es la abstracción correcta para permitir la

interacción entre hilos concurrentes?– Es deseable tener primitivas del mayor nivel

posible

• Las buenas primitivas y la buena práctica es clave!– La ejecución no es enteramente secuencial y los

bugs se hacen difíciles de encontrar; son intermitentes.

• La sincronización es una manera de coordinar multiples actividades concurrentes que comparten informaciones de estado– Veremos algunas formas en que se pueden

estructurar estas interacciones

Page 17: Exclusión Mutua y Semáforos

Semáforos

• Los semáforos son un tipo de lock generalizado– Fueron definidos por Dijkstra a finales de los 60s– Fue la principal primitiva de sincronización usada

en UNIX

• Definición: u Semáforo consta de un valor entero no negativo e implementa las sgtes. dos operaciones :– P(): una operación atómica que espera que el

semáforo se haga positivo, entonces lo decrementa en 1 » Se puede ver como la operación wait()

– V(): una operación atómica que incrementa el semáforo en 1, despertando cualquier hilo que duerma en P, si es que existe alguno» Se puede ver como la operación signal()

– La P en P() viene de “proberen” (probar) y V() viene de “verhogen” (incrementar) en Alemán

Page 18: Exclusión Mutua y Semáforos

Value=2Value=1Value=0

Los semáforos son como Enteros, solo que...

• Los semáforos son como Enteros, solo que...– No tienen valores negativos– Solo permiten las operaciones P y V – no

permiten que se lea o escriba su valor, exepto al momento de inicializarlos

– Las operaciones son atómicas» Nunca dos P’s seguidas pueden decrementar el

value por debajo de cero» De manera similar un hilo que duerma en P nunca

perderá una llamada de “weakup” (despertar) de V – aún si ambos eventos sucedieran al mismo tiempo

• Semaforó: analogía del ferrocarril– Este es un semáforo inicializado a 2 para control

de recursos:

Value=1Value=0Value=2

Page 19: Exclusión Mutua y Semáforos

Dos usos de los semáforos• Exclusión mutua (inicio value = 1)

– También llamado “Semaforo binario”.– Puede usarse para exclusión mutua:

semaphore.P();// Sección crítica va aquísemaphore.V();

• Restricción de Organización (initial value = 0)– Los locks son buenos para exclusión mutua,

pero como hacer si queremos que un hilo espere por cierta condición?

– Ejemplo: implementar un ThreadJoin que deba esperar que otro hilo termine:

valor inicial de semaforo = 0ThreadJoin { semaphore.P();}ThreadFinish { semaphore.V();}

Page 20: Exclusión Mutua y Semáforos

Producer-consumer with a bounded buffer

• Problema– Productores llenan buffer– Consumidores vacían el buffer– Se necesita sicronizacion productor/consumidor

• Se evita una interacción rígida entre productores y consumidores agregando un buffer de tamaño fijo entre ellos– El acceso al buffer debe sincronizarse– Si el buffer esta lleno los productores deben esperar– Si el buffer esta vacío los consumidores deben esperar

• Ejemplo 1: GCC compiler– cpp | cc1 | cc2 | as | ld

• Ejemplo 2: Máquina expendedora de soda– Los productores pueden poner un número limitado de

refrescos en la máquina– Los consumidores no pueden obtener refrescos si la

máquina esta vacía

Productor ConsumidorBuffer

Page 21: Exclusión Mutua y Semáforos

Condiciones de corrección de esta solución

• Condiciones de corrección :– Los consumidores deben esperar por que los

productores llenen las posiciones del buffer, si todas están vacías (restricción del orden de ejecución)

– Los productores deben esperar que los consumidores vacíen los buffers, si todos están llenos (restricción del orden de ejecución)

– Solo un hilo puede manipular la cola del buffer en determinado momento (exclusión mutua)

• Pro que necesitamos la exclusión mutua?– Las computadoras son estúpidas– Si nos vamos a la vida real, nadie intentaría sacar

comprar refrescos de una máquina a la vez que el encargado esta cergando refrescos en la máquina.

• Regla general: Usar un semáforo para cada Restricción que deba observarse– Semáforo fullBuffers; // Restricción a consumidores– Semáforo emptyBuffers;// Restricción a productores– Semáforo mutex; // exclusión mutua

Page 22: Exclusión Mutua y Semáforos

Solución completa para buffers acotados

Semáforo fullBuffer = 0; // Inicialmente, no refrescosSemáforo emptyBuffers = numBuffers;

//Inicialmente,# slots vacíosSemáforo mutex = 1; // nadie usa la máquina

Productor(item) {emptyBuffers.P(); // Esperar hasta espaciomutex.P(); // Esperar máquina libreEnqueue(item);mutex.V();fullBuffers.V(); // Decir a los consumidores

// que hay mas refresco}Consumidor() {

fullBuffers.P(); // Verificar si hay refrescomutex.P(); // Esperar máquina libreitem = Dequeue();mutex.V();emptyBuffers.V(); // Decir a los productores

que hay buffers vacíosreturn item;

}

Page 23: Exclusión Mutua y Semáforos

Discusión sobre Solucion

• Por que asimétrica?– Productores: emptyBuffer.P(), fullBuffer.V()– Consumidores: fullBuffer.P(), emptyBuffer.V()

• Es importante el orden de las P’s?– Si! el orden incorrecto puede provocar

deadlock

• Es importante el orden de las V’s?– No, a los umo podría afectar la eficiencia

• Que pasa si fueran dos productores y dos consumidores?– Habría que cambiar algo en el código?

Page 24: Exclusión Mutua y Semáforos

Monitores y Variables de Condición

• Los semáforos son un gran avance; solo hay que imaginar solucionar el problema de buffer acotado usando solamente lectura y escritura a memoria– El problema es que los semáforos tienen doble

propósito:» Se usan tanto para Mutex como para Restricciones de

Orden de Ejecución» El hecho de que invirtiendo las P’s en la solución de los

buffers acotados produzca un deadlock no es imediatamente evidente. Esto dificulta probar la exactitud

• Una idea más calra: Usar locks para exclusion mutua y Variables de Condición para restricciones de orden de ejecución

• Definicion: Monitor: Un lock y cero o más variables de condición para manejar el acceso concurrente a datos compartidos– Algunos lenguajes como Java proveen esto

intrísicamente– La mayoría de los demás lenguajes ofrecen

implementaciones de locks y variables de condición

Page 25: Exclusión Mutua y Semáforos

Monitores

• Lock: El lock provee exclusión mutua para datos– Siempre se debe adquirir antes de accesar a datos comp.– Siempre se debe liberar al terminar de usar datos comp.– El lock esta inicialmente libre

• Variable de Condición: una cola de hilos esperando por algo dentro de una sección crítica– Idea clave: posibilitar el dormir en una sección crítica

mediante una operación atómica que libera el lock y pone a dormir el hilo

– Esto a diferencia de los semáforos que no permiten dormir dentro de una sección crítica

Page 26: Exclusión Mutua y Semáforos

Ejemplo simple de monitor• Aqui vemos una cola (infinita) sincronizada

Lock lock;Condicion dataready;Queue queue;

AddToQueue(item) { //Función agregar item lock.Acquire(); // Adquirir Lock

queue.enqueue(item); // Agregar itemdataready.signal(); // Avisar al que esperalock.Release(); // Liberar Lock

}

RemoveFromQueue() { //Función remover itemlock.Acquire(); // Adquirir Lock while (queue.isEmpty()) {

dataready.wait(&lock); // No items, dormir}item = queue.dequeue(); // Tomar itemlock.Release(); // Liberar Lockreturn(item);

}

Page 27: Exclusión Mutua y Semáforos

Resumen• Concepto Importante: Operaciones atómicas

– Corren completas o no corren– Son las primitivas sobre las cuales se construyen

las primitivas de sincronización• Primitivas atómicas por hardware:

– Deshabilar interrupciones, test&set, swap, comp&swap, load-linked/store conditional

• Implementaciones de Locks– Se debe tener cuidado de no malgastar ni acaparar

los recursos de la máquina» No se deben deshabilitar la int. por mucho timepo» No se debe esperar activamente por mucho tiempo

– Idea clave: Variable de lock separada, usar mecanismos de harware para protejer las modificaciones de esa variable

• Semáforos, Monitores, and Variables de Ccondición– Implementaciones de más alto nivel, más seguras