Tema 8: Sincronización

Introducción Problemas Señales
Sincronización
Gustavo Romero López
Arquitectura y Tecnologı́a de Computadores
15 de mayo de 2015
Gustavo Romero López
Sincronización
1 / 79
Introducción Problemas Señales
Índice
1
Introducción
2
Problemas
3
Señales
Definición
Ası́ncronas
Sı́ncronas
Semáforos
UNIX
Gustavo Romero López
Sincronización
2 / 79
Introducción Problemas Señales
Lecturas recomendadas
J. Bacon
Operating Systems (9, 10, 11)
A. Silberschatz
Fundamentos de Sistemas Operativos (4, 6, 7)
W. Stallings
Sistemas Operativos (5, 6)
A. Tanuenbaum
Sistemas Operativos Modernos (2)
Gustavo Romero López
Sincronización
3 / 79
Introducción Problemas Señales
Introducción
Gustavo Romero López
Sincronización
4 / 79
Introducción Problemas Señales
Concurrencia (1)
Niveles:
Multiprogramación: gestión de multiples procesos.
Multiprocesamiento: gestión de múltiples hebras dentro de
un proceso.
Procesamiento distribuido: gestión de múltiples procesos
sobre múltiples máquinas (sin memoria compartida).
Contexto:
Entre aplicaciones: originalmente ideada con este fin.
Dentro de una aplicación: conveniente en ciertos tipos de
aplicaciones.
Sistema operativo: las mismas ventajas que aporta a las
aplicaciones son deseables aquı́.
Tipos:
Monoprocesador: los procesos se entrelazan en el tiempo
para ofrecer la apariencia de ejecución simultánea.
Multiprocesador: la ejecución de múltiples procesos se solapa
en el tiempo.
Gustavo Romero López
Sincronización
5 / 79
Introducción Problemas Señales
Concurrencia (2)
No puede predecirse la velocidad relativa de ejecución:
Eventos.
Polı́ticas del sistema operativo.
Problemas:
La compartición de recursos está plagada de peligros.
La gestión óptima de los recursos es complicada.
La localización de errores de programación es muy complicada
debido a su naturaleza no determinista y no reproducible.
Todos los sistemas comparten este tipo de problemas
aunque algunos pueden verse agravados en máquinas con
más de un procesador.
Gustavo Romero López
Sincronización
6 / 79
Introducción Problemas Señales
Conceptos importantes
Condición de carrera: situación en la que el resultado de una
operación depende de la velocidad relativa de ejecución entre
varias hebras.
Sección crı́tica: sección de código que no pueden ejecutar
varias hebras de forma concurrente.
Exclusión mutua: requisito de acceso exclusivo a recursos
durante la ejecución de una sección crı́tica.
Interbloqueo (“deadlock”): situación en la que varias hebras
son incapaces de avanzar porque todas ellas están esperando
que alguna de las otras haga algo.
Inanición: situación en la que una hebra preparada es
mantenida en dicho estado indefinidamente.
Circulo vicioso (“livelock”): situación en la que varias hebras
cambian su estado pero sin llegar a realizar trabajo útil.
Gustavo Romero López
Sincronización
7 / 79
Introducción Problemas Señales
¿Por qué sincronizar?
Las hebras cooperan porque...
Necesitan coordinar su ejecución.
El botón de detener del navegador cancela la descarga de una
página web.
La hebra del interfaz del navegador necesita avisar a la hebra
encargada de descargar la página web.
Necesitan acceder a recursos compartidos.
Varias hebras de un servidor web pueden acceder a una
caché común en memoria para ahorrar espacio y tiempo.
Gustavo Romero López
Sincronización
8 / 79
Introducción Problemas Señales
Coordinación
b2 tiene que esperar hasta
que a1 haya completado su
ejecución
ejemplos:
Navegador: ai descarga,
bi renderiza.
Servidor web: ai rellena
caché, bi responde
petición.
Gustavo Romero López
Sincronización
9 / 79
Introducción Problemas Señales
Recursos compartidos
Problema básico:
2 hebras acceden a una variable compartida.
Si la variable compartida es leida y escrita por ambas hebras
tendremos que coordinar el acceso.
En los próximos 2 temas estudiaremos...
Mecanismos para controlar el acceso a recursos compartidos:
Bajo nivel: cerrojos.
Alto nivel: semáforos, monitores, variables condición.
Protocolos para coordinar el acceso a recursos compartidos.
La gestión de la concurrencia es complicada y propensa a
errores.
En Informática se dedica una asignatura completa a su estudio.
Incluso las soluciones de algunos libros de texto son erróneas.
Gustavo Romero López
Sincronización
10 / 79
Introducción Problemas Señales
Ejemplo: cuenta bancaria (1)
procedimiento para retirar fondos de una cuenta bancaria
int retirar(CCC_t cuenta, int cantidad)
{
int saldo = conseguir_saldo(cuenta);
saldo = saldo - cantidad;
almacenar_saldo(cuenta, saldo);
return saldo;
}
procedimiento para ingresar fondos en una cuenta bancaria
int ingresar(CCC_t cuenta, int cantidad)
{
int saldo = conseguir_saldo(cuenta);
saldo = saldo + cantidad;
almacenar_saldo(cuenta, saldo);
return saldo;
}
Gustavo Romero López
Sincronización
11 / 79
Introducción Problemas Señales
Ejemplo: cuenta bancaria (2)
Supongamos un saldo inicial de 1.000e.
Estudiaremos el caso en que dos personas intentan retirar
100e a la vez de la misma cuenta desde dos cajeros
automáticos.
Imaginemos que cada procedimiento se ejecuta como una
hebra diferente.
La ejecución de las 2 hebras podrı́a entrelazarse debido a
la planificación del sistema operativo.
¿Existe algún problema?
¿Cuál será el saldo de la cuenta tras retirar fondos?
Gustavo Romero López
Sincronización
12 / 79
Introducción Problemas Señales
Ejemplo: cuenta bancaria (3)
hebra 1
saldo = conseguir saldo(cuenta);
saldo = saldo - cantidad;
almacenar saldo(cuenta, saldo);
return saldo;
hebra 2
saldo = conseguir saldo(cuenta);
saldo = saldo - cantidad;
almacenar saldo(cuenta, saldo);
return saldo;
saldo final: 800e
Gustavo Romero López
Sincronización
13 / 79
Introducción Problemas Señales
Ejemplo: cuenta bancaria (4)
hebra 1
saldo = conseguir saldo(cuenta);
saldo = saldo - cantidad;
hebra 2
saldo = conseguir saldo(cuenta);
saldo = saldo - cantidad;
almacenar saldo(cuenta, saldo);
return saldo;
almacenar saldo(cuenta, saldo);
return saldo;
saldo final: 900e
Gustavo Romero López
Sincronización
14 / 79
Introducción Problemas Señales
Condiciones de carrera
El problema ocurre porque dos hebras acceden a un recurso
compartido sin sincronizar su uso.
Las condiciones de carrera producen resultados
impredecibles.
Dependen de cuándo se ejecutan las hebras, durante
cuánto tiempo y de cuándo se produce el cambio entre
ellas, y por supuesto, de lo que estén haciendo.
El acceso concurrente a recursos compartidos debe ser
controlado.
Es la única forma de que podamos comprender el
comportamiento de los programas.
Necesitamos reintroducir el determinismo y la
reproducibilidad.
Gustavo Romero López
Sincronización
15 / 79
Introducción Problemas Señales
Operaciones atómicas
Para comprender un programa concurrente debemos conocer
qué operaciones deberı́an ser indivisibles.
Operación atómica: una operación que siempre se ejecuta
hasta completarse o que por el contrario no inicia su
ejecución.
Indivisible: no puede ser detenida a mitad y luego continuar.
Son la base fundamental sobre la que se crean programas
multihebra y sin las que no habrı́a forma de hacer que
funcionasen de manera correcta.
En la mayorı́a de los procesadores las operaciones de lectura y
escritura de una palabra de memoria son atómicas.
Sin embargo existen muchas instrucciones que no son
atómicas:
El almacenamiento de reales grandes puede no serlo.
Las instrucciones de sobre cadenas y vectores.
Gustavo Romero López
Sincronización
16 / 79
Introducción Problemas Señales
¿Qué recursos son compartidos?
Las variables locales de los procedimientos
no son compartidas.
Existen en la pila.
La pila es privada para cada hebra.
No podemos pasar un puntero a una
variable local a otra hebra ¿Por qué?
Las variables globales si son compartidas.
Se almacenan en la zona de datos estáticos.
Son accesibles por todas las hebras.
Los datos alojados dinámicamente si son
compartidos:
Se almacenan en el cima/montón (“heap”).
Son accesibles por todas las hebras.
¿Hay algún problema en utilizar punteros a
objetos compartidos? ↔ ¿dónde se
almacenan? ↔ ¿cuál es su tiempo de vida?
Gustavo Romero López
Sincronización
17 / 79
Introducción Problemas Señales
Ejemplo: recursos compartidos
¿Qué pasará al ejecutar este código en dos hebras?
void* saludo(void*)
{
while(true)
cout << ‘‘hola, soy ‘‘ << pthread self() << endl;
return NULL;
}
esto...
hola, soy 918374512
hola, soy 649128354
o esto...
hola, soy hola, soy 918374512
649128354
Condición de carrera: ambas hebras acceden al terminal que
es un recurso compartido.
Gustavo Romero López
Sincronización
18 / 79
Introducción Problemas Señales
Ejemplo: datos compartidos (1)
int a = 0, b = 0; // variables compartidas
void *suma(void*)
{
while(true)
{
a = a + 1;
b = b + 1;
}
return NULL;
}
void *resta(void*)
{
while(true)
{
a = a - 1;
b = b - 1;
}
return NULL;
}
Tras un tiempo... ¿seguirán a y b siendo iguales?
Gustavo Romero López
Sincronización
19 / 79
Introducción Problemas Señales
Ejemplo: datos compartidos (2)
int a = 0, b = 0; // variables compartidas
void *suma(void*)
{
while(true)
{
a = a + 1;
b = b + 1;
}
return NULL;
}
void *resta(void*)
{
while(true)
{
a = a - 1;
b = b - 1;
}
return NULL;
}
El código que accede a variables compartidas son secciones
crı́ticas → tras cierto tiempo a 6= b
Compartir datos y recursos induce problemas similares.
Problemas: ¿optimización? ¿volatile? ¿atomic? ¿mutex?
Gustavo Romero López
Sincronización
20 / 79
Introducción Problemas Señales
Exclusión mutua
Debemos utilizar exclusión mutua para sincronizar el acceso
a los recursos compartidos, por ejemplo, serializando el
acceso.
Cualquier código que utilice exclusión mutua para sincronizar
su ejecución es denominado sección crı́tica.
Sólo una hebra puede ejecutar su sección crı́tica.
El resto de hebras son forzadas a esperar a la entrada.
Cuando una hebra abandona su sección crı́tica otra puede
entrar.
Gustavo Romero López
Sincronización
21 / 79
Introducción Problemas Señales
Interacción entre hebras
percepción
relación
se desconocen
competencia
percepción
indirecta
(recursos
compartidos)
cooperación
por compartición
percepción directa (comunicación)
cooperación
por
comunicación
Gustavo Romero López
influencia
resultados independientes,
temporización
afectable
resultados
dependientes,
temporización
afectable
resultados
dependientes,
temporización
afectable
Sincronización
problemas
interbloqueo,
inanición
exclusión mutua,
interbloqueo,
inanición,
coherencia de datos
interbloqueo,
inanición
22 / 79
Introducción Problemas Señales
Problemas
Gustavo Romero López
Sincronización
23 / 79
Introducción Problemas Señales
Productor/consumidor con buffer ilimitado
Dos procesos repiten indefinidamente su trabajo: el productor
genera datos que introduce en un buffer. El consumidor
recuperar los datos y hace algo con ellos.
¿Hay un problema de concurrencia? =⇒ si, el consumidor no
puede consumir desde un buffer vacio.
Gustavo Romero López
Sincronización
24 / 79
Introducción Problemas Señales
Productor/consumidor con buffer limitado
¿Aparecen nuevos problemas si el buffer no es infinito? =⇒ si, el
productor no puede introducir datos en un buffer lleno.
Si hay más de un productor y/o consumidor =⇒ Deben competir
por el uso del buffer con sus iguales.
Gustavo Romero López
Sincronización
25 / 79
Introducción Problemas Señales
El problema del barbero durmiente
En una barberı́a trabaja un
barbero que tiene un único
sillón de trabajo y varias sillas
para esperar. Cuando no hay
clientes, el barbero se sienta
en una silla y se duerme.
Cuando llega un nuevo
cliente, éste o bien despierta
al barbero o, si el barbero
está afeitando a otro cliente,
se sienta en una silla. Si todas
las sillas están ocupadas por
clientes esperando se va.
Gustavo Romero López
Sincronización
26 / 79
Introducción Problemas Señales
El problema de los lectores/escritores
Cierto número de hebras acceden a memoria, unas para leer y otras
para escribir. Mientras una escriba ninguna otra podrá acceder.
Sı́ se permite a varias hebras leer simultáneamente.
¿Problemas? → Inconsistencia y competición.
3 versiones: prioridad lectores, prioridad escritores y justa.
Gustavo Romero López
Sincronización
27 / 79
Introducción Problemas Señales
El problema de la cena de los filósofos
Vida de un filósofo
while(true)
{
pensar();
coger_tenedores();
comer();
}
Requisitos:
Evitar el
intebloqueo.
Evitar la inanición.
Gustavo Romero López
Sincronización
28 / 79
Introducción Problemas Señales
Niveles de sincronización
programas
API de alto
nivel
hardware
procesos/hebras cooperativos
cerrojos, semáforos, monitores, paso de mensajes
load&store, deshabilitar interrupciones, test&set,
compare&swap
A continuación veremos cómo implementar varias primitivas de
sincronización de alto nivel:
Si las únicas operaciones atómicas disponibles son la carga y
el almacenamiento será más complicado.
Necesitamos primitivas útiles a nivel de usuario.
Gustavo Romero López
Sincronización
29 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Señales
Gustavo Romero López
Sincronización
30 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Primero las señales, después la exclusión mutua
Los dos mecanismos se diferencian sólo ligeramente.
Debemos intentar no mezclarlos.
El uso habitual de las señales es...
Una hebra desea informar a...
otra hebra
cualquier hebra
conjunto de ellas
de que ha alcanzado cierto punto de su código (IP = valor).
Ejemplo: un jefe de estación tocando el silbato.
Gustavo Romero López
Sincronización
31 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Señales
Definición: mensaje entre hebras que puede provocar la ejecución
de un procedimiento en el receptor.
Tipos:
Bandera:
Valor binario: 0/1, si/no,...
El significado de la bandera debe ser acordado entre las hebras
en comunicación.
Contador:
Cada valor puede tener un significado diferente o reflejar el
número de señales pendientes.
¿Cúando es una bandera suficiente? ¿Cuándo necesitamos un
contador? =⇒ Depende de para que usemos la bandera.
Gustavo Romero López
Sincronización
32 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Relación de precedencia (1)
hebra 1
...
sección a1
{
...
}
sección a2
{
...
}
...
hebra 2
...
sección b1
{
...
}
sección b2
{
...
}
...
¿Cómo asegurar que la sección a1 se ejecuta antes que b2? → la
sección b2 debe esperar hasta que a1 se haya ejecutado.
Gustavo Romero López
Sincronización
33 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Relación de precedencia (2)
hebra 1
...
sección a1
{
...
}
s.señalar()
sección a2
{
...
}
...
hebra 2
...
sección b1
{
...
}
s.esperar()
sección b2
{
...
}
...
¿Cómo implementar este objeto señal 1:1 (emisor/receptor)?
Gustavo Romero López
Sincronización
34 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Tipos de soluciones
Soluciones a nivel de aplicación:
No requiere instrucciones especiales del procesador ni servicios
del núcleo.
Implementado como funciones de biblioteca o dentro de la
propia aplicación.
Soluciones a nivel del núcleo:
Proporcionar llamadas al sistema que cumplan estas funciones.
Soluciones hardware:
Instrucciones especiales del procesador.
Garantizan la ejecución atómica.
La mayorı́a de los sistemas permiten usar varias de estas
soluciones.
Gustavo Romero López
Sincronización
35 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Señales en el problema productor/consumidor
Problemas que podremos resolver empleando señales:
El productor debe avisar al consumidor para que pueda
utilizar el objeto que acaba de introducir en el buffer.
El consumidor debe avisar al productor de que ha
consumido un objeto del buffer y por lo tanto ha quedado
un hueco libre.
El productor debe esperar cuando el buffer está lleno.
El consumidor debe esperar cuando el buffer está vacı́o.
Gustavo Romero López
Sincronización
36 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Solución al problema productor/consumidor (v1)
const int N = 10; // tama~
no del buffer
int buffer[N];
// almacén de datos
int contador = 0; // posición a producir/consumir
void* productor(void*)
{
while (true)
{
while (contador == N);
buffer[contador] = producir();
contador = contador + 1;
}
}
void* consumidor(void*)
{
while (true)
{
while (contador == 0);
contador = contador - 1;
consumir(buffer[contador]);
}
}
¿Funciona? → No, condición de carrera en la variable
compartida contador.
Gustavo Romero López
Sincronización
37 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Solución al problema productor/consumidor (v2)
const int N = 10;
int buffer[N];
int entra = 0;
int sale = 0;
//
//
//
//
número de elementos
almacén de datos
posición para introducir
posición para sacar
void* productor(void*)
{
while (true)
{
while ((entra + 1) % N == sale);
buffer[entra] = producir();
entra = (entra + 1) % N;
}
}
void* consumidor(void*)
{
while (true)
{
while (entra == sale);
consumir(buffer[sale]);
sale = (sale + 1) % N;
}
}
¿Funciona? ¿es eficiente? ¿es escalable?
¿Podemos utilizar todos los elementos del buffer? → No, sólo N − 1.
Gustavo Romero López
Sincronización
38 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Escalabilidad
¿Cuándo se dice que una solución del problema
productor/consumidor es escalable?
Cuando funciona de forma efectiva para:
p ≥ 1 productores.
c ≥ 1 consumidores.
un buffer de 0 < N < MAXINT elementos.
La solución anterior no es escalable porque...
Sólo parcialmente correcto para p = 1 y c = 1.
Incorrecto para N = 1.
Gustavo Romero López
Sincronización
39 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Eficiencia
¿Cuándo se dice que una solución del problema
productor/consumidor es eficiente?
Cuando funciona de forma rápida y con poca sobrecarga.
La solución anterior no es eficiente porque...
La sincronización mediante espera ocupada desperdicia el
tiempo del procesador.
El planificador puede prevenir la ineficiente espera ocupada.
Sólo es útil si conocemos que la espera ocupada va a ser corta.
¿Cómo mejorar esta solución?
Utilizar la API del núcleo para evitar la espera ocupada.
Gustavo Romero López
Sincronización
40 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Interfaz del núcleo para evitar la espera ocupada
block()
sleep() → bloquear/dormir de UNIX.
unblock()
wakeup() → desbloquear/despertar de UNIX.
yield()
pthread yield() → ceder el procesador entre hebras.
sched yield() → ceder el procesador entre procesos UNIX.
Gustavo Romero López
Sincronización
41 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
block() → bloquear() del núcleo
bloquear(hebra_actual)
{
hebra_actual.estado = bloqueado ;
siguiente_hebra = planificador();
hebra_actual = cambio_de_hebra(siguiente_hebra);
hebra_actual.estado = ejecutando ;
}
desbloquear(una_hebra)
{
if (una_hebra.estado == bloqueado )
una_hebra.estado = preparado ;
}
Solución para el modelo de 3 estados.
¿Cómo implementarı́a yield? −→ modificando bloquear...
Gustavo Romero López
Sincronización
42 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
API del núcleo aplicada al problema productor/consumidor
const int N = 100; // tama~
no del buffer
int buffer[N];
// almacén
int contador = 0; // número de elementos en el buffer
void* productor(void*)
void* consumidor(void*)
{
{
while (true)
while (true)
{
{
if (contador == N)
if (contador == 0)
bloquear(productor);
bloquear(consumidor);
buffer[contador++] = producir();
consumir(buffer[--contador]);
desbloquear(productor);
desbloquear(consumidor);
}
}
}
}
Mejora la eficiencia: deja de utilizar espera ocupada, pero...
Problemas: condición de carrera (contador) e interbloqueo 1 .
1
contador == 0 + if del consumidor + N ejecuciones de productor
Gustavo Romero López
Sincronización
43 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
En resumen... la API del núcleo puede ser insuficiente
El uso del API del núcleo puede ser peligroso.
Ver la condición de carrera del ejemplo anterior.
Serı́a mejor utilizar objetos señal u objetos
sincronización con métodos apropiados.
Hasta ahora hemos intentado enviar señales sin
éxito mediante...
espera ocupada a nivel de usuario.
bloqueo a nivel del núcleo.
Gustavo Romero López
Sincronización
44 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Objeto señal
implementación a nivel de usuario
Implementación que emplea una
variable lógica s compartida
entre hebras.
¿Qué
podrı́a
suceder
si
o.se~
nalar() es invocado varias
veces antes que o.esperar()?
→ se pierden señales.
¿Qué
podrı́a
suceder
si
o.esperar()
es
invocado
antes que o.se~
nalar()?
→ espera ocupada, se deberı́a
dejar libre el procesador.
¿Funcionarı́a en cualquier tipo
de sistema? → no, exige
memoria compartida.
¿Es eficiente y escalable?
Gustavo Romero López
Sincronización
45 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Objeto señal: implementación a nivel usuario (v1)
class se~
nal
{
public:
se~
nal(): activa(false) {}
¿Pierde señales?
¿Eficiente?
¿Escalable?
void se~
nalar() { activa = true; }
void esperar()
{
while (activa == false);
activa = false;
}
private:
bool activa;
};
Gustavo Romero López
Sincronización
46 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Objeto señal: implementación a nivel usuario (v2)
class se~
nal
{
public:
se~
nal(): activa(false) {} // inicialización
¿Pierde señales?
¿Eficiente?
¿Escalable?
Parecido a la espera
ocupada.
void se~
nalar()
{
activa = true;
yield();
}
// exige cooperación
void esperar()
{
while (activa == false)
yield();
// evita espera ocupada
activa = false;
}
private:
bool activa;
};
Gustavo Romero López
Sincronización
47 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
¿Son escalables estas soluciones? (1)
¿Puede más de una hebra se~
nalar y/o
esperar?
No, de otra forma ya no serı́a un objeto señal 1:1 sino n:m con
n > 1, m > 1 o ambos.
Múltiples usos de un mismo objeto señal dentro
del un mismo par de hebras.
No es efectivo ni eficiente.
Multiples objetos señal dentro del un mismo par
de hebras.
No es eficiente.
Gustavo Romero López
Sincronización
48 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
¿Son escalables estas soluciones? (2)
Múltiples usos de un mismo objeto señal dentro del un
mismo par de hebras.
No es efectivo ni eficiente ↔ ¿por qué?2
2
solución: o1, o2 y o3.
Gustavo Romero López
Sincronización
49 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Objeto señal: implementación a nivel del núcleo (v3)
class se~
nal
{
public:
se~
nal(): bloqueada(ninguna) {}
¿Pierde señales?
void se~
nalar()
{
desbloquear(bloqueada);
}
¿Eficiente?
¿Escalable?
void esperar()
{
bloqueada = esta;
bloquear(bloqueada);
bloqueada = ninguna; // ¿necesario?
}
private:
hebra bloqueada;
};
Gustavo Romero López
Sincronización
50 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Objeto señal: implementación a nivel del núcleo (v4)
class se~
nal
{
private:
bool activa;
hebra bloqueada;
public:
se~
nal():
activa(false),
bloqueada(ninguna) {}
void se~
nalar()
{
activa = true;
if (bloqueada != ninguna)
desbloquear(bloqueada);
2 }
void esperar()
{
while(activa == false)
{
1
bloqueada = esta;
3
bloquear(esta);
}
activa = false;
}
};
¿Pierde señales?
¡Eficiente!
¡Escalable!
¡Condición de carrera!
Gustavo Romero López
Sincronización
51 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Resumen
Problemas aún sin resolver:
Las operaciones se~
nalar() y esperar() deberı́an ser
atómicas.
Evitar la espera ocupada a nivel de usuario.
Sobre las soluciones vistas...
Todas pueden perder señales.
Repetidas operaciones se~
nalar() pueden sobreescribir señales
todavı́a no consumidas.
La operación se~
nalar() es ası́ncrona (no bloqueante) en
caso de que no haya otra hebra bloqueada mediante
esperar().
Existe el peligro de desbordar a otra hebra (servidor).
Gustavo Romero López
Sincronización
52 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Reconocimiento para evitar el desbordamiento
Observaciones:
se~
nalar() es ası́ncrono.
¿Hay alguna solución más obvia? −→ sincronı́a.
¿Protege frente a clientes maliciosos? −→ no.
Gustavo Romero López
Sincronización
53 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Objeto señal sı́ncrona
class se~
nal_sı́ncrona
{
public:
se~
nal_sı́ncrona():
activa(false),
se~
naladora(ninguna),
esperadora(ninguna) {}
void se~
nalar()
{
activa = true;
if (esperadora != ninguna)
desbloquear(esperadora);
else
{
se~
naladora = esta;
2
bloquear(se~
naladora);
se~
naladora = ninguna;
}
Gustavo Romero López
}
void esperar()
{
while(activa == false)
{
1
esperadora = esta;
3
bloquear(esperadora);
esperadora = ninguna;
}
activa = false;
desbloquear(se~
naladora)
}
private:
bool activa;
hebra se~
naladora;
hebra esperadora;
};
Sincronización
54 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
¿Son las banderas superfluas?
... dado que existen soluciones más potentes
No.
Casi todos los sistemas utilizan banderas.
Ejemplo: varias interrupciones sucesivas podrı́a provocar varios
cambios de hebra sucesivos.
Supongamos un sistema en que las interrupciones se producen
con mucha frecuencia y las prioridades pueden anidarse →
¿Cómo puede ser beneficioso utilizar banderas?
Gustavo Romero López
Sincronización
55 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Ejemplo de uso de banderas
Planificación perezosa y cambio de hebra: esperar hasta que
haya que volver al espacio de usuario.
Cada manejador de interrupción en el que pueda ser necesario
planificar activa la bandera de planificación.
Gustavo Romero López
Sincronización
56 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Relación de precedencia mútua (1)
hebra 1
...
sección a1
{
...
}
sección a2
{
...
}
...
hebra 2
...
sección b1
{
...
}
sección b2
{
...
}
...
¿Cómo conseguir que a1 <* b2 y que b1 <* a2?
Gustavo Romero López
Sincronización
57 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Relación de precedencia mútua (2)
hebra 1
...
sección a1
{
...
}
s1.señalar()
hebra 2
...
sección b1
{
...
}
s2.señalar()
s2.esperar()
sección a2
{
...
}
...
s1.esperar()
sección b2
{
...
}
...
¿Funciona? ¿Puede hacerse mucho mejor?
Gustavo Romero López
Sincronización
58 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Relación de precedencia mútua (3)
Ventajas:
Inconvenientes:
No necesitamos nuevos
mecanismos para un
problema parecido.
Complicado e ineficiente
=⇒ no es escalable.
Es muy fácil olvidar la
sincronización con una
nueva hebra =⇒ engorroso
y propenso a errores.
Pobre rendimiento debido
a que podrı́amos ahorrar
llamadas al sistema =⇒
no es eficiente.
Conclusión: necesitamos un mecanismo más efectivo.
Gustavo Romero López
Sincronización
59 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Sincronización “pura”
hebra 1
...
sección a1
{
...
}
s.sincronizar()
sección a2
{
...
}
...
hebra 2
...
sección b1
{
...
}
s.sincronizar()
sección b2
{
...
}
...
Problema: ¿Cómo implementar la sincronización de dos hebras?
Gustavo Romero López
Sincronización
60 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Objeto sincronización para 2 hebras
void sincronizar()
{
if (!activo)
{
activo = true;
bloqueada = actual;
bloquear(actual);
class sincronización
{
private:
bool activo;
hebra bloqueada;
// soy el primero
// esperar a la otra
}
else
// soy el segundo
{
activo = false;
// reinicializar
desbloquear(bloqueada); // liberar a la otra
public:
sincronización():
activo(false),
bloqueada(ninguna)
{
}
}
}
};
¿Es reutilizable?
¿Puede generalizase para más de dos hebras?
Gustavo Romero López
Sincronización
61 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Ejemplo de sincronización entre n hebras (barrera)
// problema numérico resuelto
// mediante ecuaciones diferenciales
sincronización s;
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
{
temp[i, j] = matriz[i - 1, j] + matriz[i + 1, j];
}
s.sincronizar();
for (int i = 0; i < N; ++i)
for (int j = 0; j < N; ++j)
{
matriz[i, j] = temp[i, j];
}
s.sincronizar();
Gustavo Romero López
Sincronización
62 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Barrera
Barrier
B
C
A
B
B
C
Barrier
Process
A
Barrier
A
D
D
Time
D
Time
(a)
C
Time
(b)
(c)
Uso de una barrera:
(a) Las hebras se aproximan a la barrera.
(b) Al llegar a la barrera las hebras se bloquean.
(c) La última hebra llega y provoca el desbloqueo de todas.
Gustavo Romero López
Sincronización
63 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Patrones de señalización: muchos a uno
Significado: la hebra azul sólo puede puede continuar tras un
punto de espera si las tres hebras verdes han alcanzado
cierto punto especı́fico de su código.
Gustavo Romero López
Sincronización
64 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Patrones de señalización: uno a muchos
Significado: las hebras azules sólo puede puede continuar si
la hebra verde han alcanzado cierto punto de su código.
Gustavo Romero López
Sincronización
65 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Patrones de señalización: muchos a muchos
Gustavo Romero López
Sincronización
66 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Otros patrones de señalización
La hebra azul sólo puede continuar si alguna de las dos
hebras verdes alcanza cierto punto de su código.
Problema: ¿Cómo y dónde almacenar temporalmente las
señales?
Gustavo Romero López
Sincronización
67 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Almacenamiento temporal de señales
Una nueva señal sobreescribe a las anteriores: banderas o
semáforos binarios.
Ventaja: se reacciona sólo a la señal más nueva.
Inconveniente: podrı́amos perder señales necesarias.
Cada nueva señal es almacenada temporalmente hasta que
pueda ser consumida: semáforos con contador.
Ventaja: se reacciona a toda señal.
Inconveniente:
una fuente de señales deficiente podrı́a desbordar el sistema.
Gustavo Romero López
Sincronización
68 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Semáforos con contador (Dijkstra)
objetos señal del núcleo
Definición: un semáforo es una variable entera, que aparte de
la inicialización, sólo puede ser modificada mediante dos
operaciones atómicas y mutuamente excluyentes.
p() / decrementar() / esperar()
Operación que decrementa el valor del semáforo.
v() / incrementar() / señalar()
Operación que incrementa el valor del semáforo.
Gustavo Romero López
Sincronización
69 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Semáforos con contador
¿Cómo diseñar e implementar los semáforos con
contador?
Para evitar la espera ocupada...
Cuando una hebra debe esperar ante un
semáforo =⇒ poner a la hebra llamadora en una
cola de bloqueados en espera de un evento.
La ocurrencia de un evento puede comunicarse
mediante el incremento del valor del semáforo.
Gustavo Romero López
Sincronización
70 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Semáforos con contador
Significado de los semáforos con contador:
Un valor positivo indica el número señales
emitidas.
Un valor negativo indica el número de hebras
que esperan por una señal −→ longitud de la
cola de hebras bloqueadas en espera de una
señal.
Si el contador es cero ni hay señales emitidas ni
hay hebras esperando que se produzca una señal.
Gustavo Romero López
Sincronización
71 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Semáforos con contador
class semáforo
{
public:
semáforo(int _valor = 0):
valor(_valor),
bloq(vacia) {}
void p() // esperar()
{
valor = valor - 1;
if (valor < 0)
{
bloq.a~
nadir(esta);
bloquear(esta);
}
Gustavo Romero López
}
void v() // se~
nalar()
{
hebra h;
valor = valor + 1;
h = bloq.borrar primero();
desbloquear(h);
}
private:
int valor;
cola<hebra> bloq;
};
Sincronización
72 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Señales UNIX
Pueden lanzarse mediante la orden kill del intérprete de
órdenes y la llamada al sistema kill().
No tienen un significado ni un interfaz común.
Hay cuatro versiones diferentes:
System V unreliable
BSD
System V reliable
POSIX
El uso de las señales puede conducir a condiciones de carrera.
La programación con señales es engorrosa.
Gustavo Romero López
Sincronización
73 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Lista de señales UNIX (POSIX.1)
Signal
Value
Action Comment (Value = alpha/sparc,i386/ppc/sh,mips)
------------------------------------------------------------------------SIGHUP
1
Term
Hangup detected on controlling terminal
or death of controlling process
SIGINT
2
Term
Interrupt from keyboard
SIGQUIT
3
Core
Quit from keyboard
SIGILL
4
Core
Illegal Instruction
SIGABRT
6
Core
Abort signal from abort(3)
SIGFPE
8
Core
Floating point exception
SIGKILL
9
Term
Kill signal
SIGSEGV
11
Core
Invalid memory reference
SIGPIPE
13
Term
Broken pipe: write to pipe with no readers
SIGALRM
14
Term
Timer signal from alarm(2)
SIGTERM
15
Term
Termination signal
SIGUSR1 30,10,16 Term
User-defined signal 1
SIGUSR2 31,12,17 Term
User-defined signal 2
SIGCHLD 20,17,18 Ign
Child stopped or terminated
SIGCONT 19,18,25
Continue if stopped
SIGSTOP 17,19,23 Stop
Stop process
SIGTSTP 18,20,24 Stop
Stop typed at tty
SIGTTIN 21,21,26 Stop
tty input for background process
SIGTTOU 22,22,27 Stop
tty output for background process
Gustavo Romero López
Sincronización
74 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Reparto de señales en UNIX
Gustavo Romero López
Sincronización
75 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Tipos de señales UNIX
Las señales UNIX pueden recibirse...
sı́ncrona
o
ası́ncronamente
Las señales sı́ncronas suelen enviarlas la propia hebra/proceso:
violación del espacio de direcciones.
excepción de división entre cero.
reservadas al usuario.
Las señales ası́ncronas suele enviarlas otras hebras/procesos:
órden kill
llamada al sistema kill()
CTRL+C: SIGINT −→ terminar proceso
CTRL+Z: SIGSTOP −→ suspender proceso
expira el tiempo del temporizador
Gustavo Romero López
Sincronización
76 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
API de las señales UNIX
#include
#include
#include
#include
<sys/types.h>
<pthread.h>
<unistd.h>
<signal.h>
//
//
//
//
pid_t
pthread_t
getpid
kill pthread_kill raise signal
// instala la función ‘‘manejador’’ como manejador
// de se~
nal para la se~
nal ‘‘signum’’
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t manejador);
// envia la se~
nal ‘‘signum’’ a el proceso ‘‘pid’’
int kill(pid_t pid, int signum);
// envı́a la se~
nal ‘‘signum’’ a el proceso actual
// equivalente a kill(getpid(), signum)
int raise(int signum);
// envı́a la se~
nal ‘‘signum’’ a la hebra ‘‘hebra’’
int pthread_kill(pthread_t hebra, int signum);
Gustavo Romero López
Sincronización
77 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
Manejadores de señal UNIX
El manejador por defecto, ¿se ejecuta en modo
usuario o modo núcleo? → depende de quien
maneje el evento.
Manejadores definidos por el usuario:
Implemente manejadores de señal cortos.
Tenga mucho cuidado al utilizar señales.
Muchos sistemas UNIX exigen reinstalar el manejador de señal
para poder volver a utilizarlo por segunda vez.
¿Detecta una posible condición de carrera?
Gustavo Romero López
Sincronización
78 / 79
Introducción Problemas Señales
Definición Ası́ncronas Sı́ncronas Semáforos UNIX
alarma.c
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int F R E C U E NCI A_A LARM A = 1; // segundos
void a trapar_alarma ( int ) ; // d e c l a r a c i ó n a n t i c i p a d a
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void in s talar_alarma () // i n s t a l a d o r del m a n e j a d o r de se ~
n al y alarma
{
signal ( SIGALRM , atrapar_alarma ) ; // e s t a b l e c e r m a n e j a d o r de la se ~
n al
alarm ( F R ECU ENC IA_ ALA RMA ) ;
// e s t a b l e c e r alarma
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void a trapar_alarma ( int i ) // m a n e j a d o r de se ~
n al
{
i ns ta la r_alarma () ; // r e i n s t a l a r tras cada alarma
printf ( " ¡ ¡ ¡ ALARMA !!!\ n " ) ;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int main ()
{
i ns ta la r_alarma () ; // instalar alarma por primera vez
for (;;) ;
// bucle infinito
}
Gustavo Romero López
Sincronización
79 / 79