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 lista L
  • Cuando otro proceso señaliza sobre S, alguno de los bloqueados sale de L según algún algoritmo de prioridad

modalidades-semaforos

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 y semop

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.

productor-consumidor

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

rc_condicional

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

monitores

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

TipoCarácterClase de Señal
SASeñales automáticasSeñales implícitas, incluidas por el compilador. No hay que programar send
SCSeñalar y continuarSeñal explícita no desplazante. El proceso señalador no sale
SXSeñalar y salirSeñal explícita desplazante. El proceso señalador sale. Send debe ser la última instrucción del monitor
SWSeñalar y esperarSeñal explícita desplazante. El proceso señalador sale y es enviado a la cola de entrada del monitor
SUSeñalar con urgenciaSeñ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étodos wait, 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.