Tema 3: Modelos teóricos de control de la concurrencia en sistemas de memoria común
Podemos encontrar varios niveles de abstracción:
- Lenguaje: monitores, regiones críticas
- Sistema: semáforos
- Memoria: espera ocupada
Problema de de la exclusión mutua
Sólo un proceso ejecuta su sección crítica. Para ello, se introducen protocolos de E/S, que suelen requerir variables adicionales.
Un proceso nunca puede pararse en la ejecución de los protocolos o en la sección crítica. No habrá bloqueos, si varios procesos quieren acceder a la sección crítica, alguno lo conseguirá eventualmente.
Tampoco habrá permanencia indefinida de procesos en el pre-protocolo, todos deben tener éxito para entrar en la sección crítica. Es decir no habrá procesos ansiosos.
Igualmente, no hay contenciones, un único proceso deseoso de acceder a la sección crítica lo logrará. Progreso en la ejecución.
Espera ocupada
Se logra la EM mediante protocolos de entrada que consumen ciclos de CPU. Pueden ser:
- Soluciones software, las únicas instrucciones atómicas de bajo nivel que consideran son load/store
- Algoritmos de Dekker, Peterson, Knuth, Kesell, Eisenberg-MacGuire, Lamport
- Soluciones hardware, utilizan instrucciones específicas de lectura-escritura o intercambio de datos en memoria común cuya ejecución es garantizada como de carácter atómico
Soluciones algorítmicas con variables comunes
Vea
Turnos.java
Son poco eficientes y muy complejos, en arquitecturas modernas puede funcionar de forma incorrecta y actualmente no se utilizan. Se concretan en los algoritmos de Dekker, Kesell, Peterson, Lamport, Eisenberg-McGuire...
Inconvenientes
- Utilizan espera ocupada
- Requieren un análisis y programación muy cuidadosos
- Están muy ligados a la máquina en la que se implementan
- No son transportables
- No dan una interfaz directa al programador
- Son poco estructurados y difíciles de entender a un número arbitrario de entidades concurrentes
Semáforos
Definición
Es una variable S
entera que toma valores no nogativos y sobre la que se pueden realizar dos operaciones. Introducidos por Dijsktra en 1965.
Operaciones soportadas:
Wait(S)
:- Si \(S>0\), entonces \(S = S - 1\)
- En otro caso, la entidad concurrente es suspendida sobre \(S\), en una cola asociada
Signal(S)
:- Si hay una entidad concurrente suspendida, se le despierta
- En otro caso \(S = S + 1\)
Generalidades
Wait
y Signal
son operaciones atómicas, el valor inicial es \(> 0\), Signal
despierta algún proceso, no especificado, habitualmente FIFO.
Hipotesis de corrección
- Semáforos generales: \(S\ge0\)
- Semáforos binarios: \(S = {0, 1}\)
Ecuaciones de invarianza, deben ser satisfechas por cualquier implementación del concepto de semáforo:
\(S\ge0\)
\(S=S_0 + |Signals| - |Waits|\)
Nota: |
Signals
| = número de signals ejecutados, mismo para |Waits
|
Implementación
Type semaforo=record of
S: integer
L: lista_de_procesos
end
La variable S
mantiene el valor actual del semáforo, y L
es una estructura de datos, dinámica.
- Cuando \(S=0\) y un proceso llama a
Wait
es bloqueado y mantenido en la listaL
- Cuando otro proceso señaliza sobre
S
, alguno de los bloqueados sale deL
según algún algoritmo de prioridad
Semáforos en C
C dispone de una biblioteca sem.h
en el marco de facilidades IPC:
- Define conjuntos de semáforos
- Semántica muy diferente al estándar de Dijkstra
- Son mas expresivos
- Funciones
semget
,semctl
ysemop
Semáforos en Java
No se dispusieron como primitivos hasta 1.5, asi mismo, java proporciona primitivas de control de la exclusión mutua en el acceso concurrente a objetos (cerrojos).
Java permite forzar la ejecución de métodos en exclusión mutua.
private static Semaphore s = new Semaphore(1);
//...
s.acquire(); // wait(S)
s.release(); // signal(S)
Barreras con semáforos
una barrera es un punto del código que ninguna entidad concurrente sobrepasa hasta que todas han llegado a ella.
Inconvenientes de los semáforos
- Bajo nivel y no estructurados
- Balanceado incorrecto de operaciones
- Semántica poco ligada al contexto
- Mantenimiento complejo de códigos que hacen uso exhaustivo de semáforos
Sincronización compleja: Productor-Consumidor
Idealmente tenemos un buffer de comunicación infinito.
- Productor, pueden insertar tantos datos como desee
- Consumidor, solo puede extraer de un buffer con datos presentes
Usamos un semáforo Elements
para controlar al consumidor, dos punteros que indican las posiciones donde se extrae o se inserta: In_Ptr
, Out_Ptr
y acceso a las variables de puntero en EM mediante el uso de un semáforo binario.
Regiones críticas
Una región crítica es una sección crítica cuya ejecución bajo EM está garantizada.
Más alto nivel que los semáforos, agrupa acceso a recursos comunes (variables) en regiones (recursos). Variables compartidas etiquetadas como compartidas o recurso.
Un proceso no puede entrar en una región donde ya hay otro, debe esperar. Permiten detectar accesos indebidos en tiempo de compilación.
Semántica
Las entidades concurrentes sólo pueden acceder a variables compartidas dentro de una región crítica, y lo hará en tiempo finito. En tiempo t
solo puede haber un proceso dentro de una región crítica.
Una entidad concurrente pasa un tiempo finito dentro de una región crítica, y posteriormente la abandona, un proceso que no pueda entrar en una RC es puesto en espera.
En general, la gestión de la cola de espera es equitativa.
Inconvenientes
Su anidamiento puede producir interbloqueos, no dan soporte para resolver la sincronización.
Mejora: regiones críticas condicionales
Regiones críticas condicionales
Tienen una sintaxis sencilla, aunque una semantica no tanto.
Sintaxis
region identificador when condicion do
begin
s1;
...;
sm;
end
Semántica
Un proceso que desea ejecutar la región crítica debe conseguir el acceso en EM a la misma. Si ya está ocupada, espera sobre la cola principal.
El proceso que logra la EM a la sección crítica entra en ella y evalúa la condición (1).
- Si la condición es cierta, se ejecuta la lista de instrucciones
s1,...,sn
- Si no, el proceso es enviado (2), a la cola de eventos, en espera a que la condición sea cierta. La exclusión mutua sobre la región crítica se libera
Cuando un proceso termina de ejecutar la sección crítica, la región comprueba si hay procesos en la cola de eventos para los cuáles la re-evaluación de la condición sea cierta.
- Si no hay ninguno, la región da paso al proceso que está en el frente de la cola principal
- Si hay alguno, se le da acceso a la región
Regiones críticas en C/C++ y Java
En C/C++ no existen como tales, pueden simularse a partir de otras primitivas, como monitores.
En Java, existen mediante bloques synchronized
, requieren de un objeto para proveer el bloqueo. El modelo RCC se puede simular mediante un monitor construido con la API de concurrencia estándar.
Monitores
Un monitor es una construcción sintáctica de un lenguaje de programación concurrente que encapsula un recurso crítico a gestionar en exclusión mutua, junto con los procedimientos que lo gestionan. Por tanto se da:
-
Centralización de recursos
-
Estructuración de los datos
Características
- Encapsulamiento de los datos y alta estructuración
- Exclusión mutua de procesos internos por definición
- Sincronización mediante el uso del concepto de señal
- Compacidad y eficacia
- Transparencia al usuario
- Tienen las mismas capacidades que los semáfors, son más expresivos
Estrutura sintáctica
type nombre=monitor
declaraciones de variables
procedure entry P1(...)
begin...end;
...
procedure entry PN(...)
begin...end;
begin
codigo de inicialización
end
### Modelo semántico El acceso de las entidades concurrentes al monitor es en exclusión mutua. La variables de condición proveen sincronización.
Una entidad concurrente puede quedar en espera sobre una variable de condición. En ese momento, la exclusión mutua se libera y otras entidades pueden acceder al monitor.
Disciplinas de señalización más usadas:
- Señalar y salir, la entidad que señaliza abandona el monitor
- Señalar y seguir, la entidad que señaliza sigue en el monitor
### Variables de condición, señales
Permiten sincronizar a las entidades concurrentes que acceden al monitor.
nombre_variable: condition;
Operaciones soportadas:
-
wait(variable_conditcion)
, la entidad concurrente que está dentro del monitor y hace la llamada es suspendida en una cola FIFO asociada a la variable en espera de que se cumpla la condición. Exclusión mutua liberada -
send(variable_condicion)
, proceso situado al frente de la cola asociada a la variable despertado -
non_empty(variable_condicion)
, devuelve verdadero si la cola asociada a la variable no está vacía
NOTA: en ocasiones, la sintaxis podrá ser
c.wait
,c.send
para una variable de condición c
Disciplinas de señalización
Tipo | Carácter | Clase de Señal |
---|---|---|
SA | Señales automáticas | Señales implícitas, incluidas por el compilador. No hay que programar send |
SC | Señalar y continuar | Señal explícita no desplazante. El proceso señalador no sale |
SX | Señalar y salir | Señal explícita desplazante. El proceso señalador sale. Send debe ser la última instrucción del monitor |
SW | Señalar y esperar | Señal explícita desplazante. El proceso señalador sale y es enviado a la cola de entrada del monitor |
SU | Señalar con urgencia | Señal explícita desplazante. El proceso señalador sale y es enviado a una cola de procesos urgente, prioritaria en la entrada |
Monitores en C/C++ y Java
C++11 proporciona clases que dan soporte a cerrojos y variables de condición. Es posible implantar monitores con un uso combinado.
En java, todo objeto es un monitor potencial.
-
Clase
Object
: métodoswait
,notify
,notifyAll
. -
Clases con métodos
synchronized
Solo soporta variables de condición a partir de Java 1.5, en otro caso, hay que simularlo. Equivalen a una única variable de condición.