Está en la página 1de 34

Tema 11.

Programación Concurrente y Sincronización

1. Introducción
2. Ejecución concurrente
3. Representación de procesos
4. Hilos en Java
5. Hilos en C/POSIX.1c
6. Sincronización

La mayor parte está basada en material original de Alan Burns y Andy Wellings
(Universidad de York ) y Juan Antonio de la Puente (DIT UPM).

Tema 11 Programación Concurrente y Sincronización 1


Introducción

• La mayoría de los sistemas de tiempo real


son concurrentes por naturaleza
• Las actividades del entorno evolucionan
en paralelo
– Paralelismo físico
• Programación concurrente:
– Marco para expresar de forma abstracta la
ejecución de actividades potencialmente
paralelas
– y sin ocuparse de los detalles de
implementación

Tema 11 Programación Concurrente y Sincronización 2


Sistemas Concurrentes

• Un sistema concurrente se compone de


un conjunto de procesos autónomos que
se ejecutan, desde un punto de vista
lógico, en paralelo
• Los lenguajes de programación
concurrente incorporan la noción de
proceso:
– Cada proceso tiene un flujo de control
independiente
– Las instrucciones de los procesos se ejecutan
intercalándose unas con otras
• Paralelismo lógico

Tema 11 Programación Concurrente y Sincronización 3


Objetivos

• Ilustrar los principios de la programación


concurrente
• Repasar los diferentes modelos de
creación y ejecución de procesos
concurrentes
• Mostrar cómo crear procesos
concurrentes en Java y C/POSIX

Tema 11 Programación Concurrente y Sincronización 4


Ejecución concurrente

• En la práctica, la implementación de una


colección de procesos puede tomar
diversas formas:
– Multiprogramación
• si multiplexan sus ejecuciones en un solo procesador
– Multiproceso
• en un multiprocesador con memoria compartida
(fuertemente acoplado)
– Procesamiento distribuido
• en un sistema con varios procesadores que no
comparten memoria (débilmente acoplado)

Tema 11 Programación Concurrente y Sincronización 5


Núcleo de ejecución (RTSS)
• Los procesos concurrentes se ejecutan con la
ayuda de un núcleo de ejecución (run-time
support system)
– Planificador (scheduler) del sistema operativo
• Se encarga de la creación, terminación y
multiplexado de los procesos
• Opciones del núcleo:
– Desarrollado como parte de la aplicación (Modula-2)
– Incluido en el entorno de ejecución del lenguaje (Ada,
Java)
– Parte de un sistema operativo de tiempo real (POSIX)
– Microprogramado en el procesador (occam2)
• El método de planificación utilizado afecta al
comportamiento temporal del sistema
Tema 11 Programación Concurrente y Sincronización 6
Procesos e hilos
• Todos los sistemas operativos soportan procesos
– Cada proceso se ejecuta en una máquina virtual distinta
• Algunos sistemas operativos soportan procesos
ligeros (hilos o threads)
– Todos los hilos de un proceso comparten la misma
máquina virtual
– Tienen acceso al mismo espacio de memoria
– El programador o el lenguaje deben proporcionar
mecanismos para evitar interferencias
– La concurrencia puede estar soportada por
• el lenguaje: Java, Ada, occam2
• el sistema operativo: C/POSIX

Tema 11 Programación Concurrente y Sincronización 7


Lenguajes concurrentes

• Tienen elementos de lenguaje para:


– Crear procesos concurrentes
– Sincronizar su ejecución
– Comunicarse entre sí
• Las procesos pueden:
– Ser independientes
– Cooperar para un fin común
– Competir por el uso de los recursos
• Si cooperan o compiten necesitan
comunicarse y/o sincronizar sus
actividades

Tema 11 Programación Concurrente y Sincronización 8


Clasificación de los procesos
• Estructura: • Iniciación:
– Estática – Paso de parámetros
– Dinámica – IPC
• Nivel léxico: • Terminación
– Plano – Al completar la
– Anidado ejecución
• Granularidad – Auto-terminación
– Aborto explícito
– Gruesa
– Excepción no prevista
– Fina
– Si ya no es necesario
– Nunca

Tema 11 Programación Concurrente y Sincronización 9


Procesos anidados

• Es posible establecer jerarquías de


procesos y establecer relaciones entre
procesos
• Relación padre-hijo:
– Un proceso crea otro proceso
– El padre puede tener que esperar mientras el
hijo es creado e inicializado
• Relación tutor-pupilo:
– (guardian-dependent)
– Un proceso es afectado por la terminación de
otro proceso
– El tutor no puede terminar hasta que lo hayan
hecho todos sus pupilos
Tema 11 Programación Concurrente y Sincronización 10
Los procesos y la programación orientada a objetos

• Objetos activos:
– Ejecutan acciones espontáneamente
– Contienen uno o más hilos
• Objetos reactivos:
– Sólo ejecutan acciones cuando son invocados
por otros objetos
– Objetos pasivos:
• Sin control de acceso (se ejecutan sin restricciones)
– Recursos:
• Con control de acceso según su estado interno
• Necesitan un agente de control:
– Recursos protegidos: agente pasivo
» Ej: semáforo
– Servidores: agente activo

Tema 11 Programación Concurrente y Sincronización 11


Representación de procesos

• Hay varias formas de expresar la


ejecución concurrente en los lenguajes de
programación:
– Corrutinas
– Bifurcación y unión (fork/join)
– cobegin/coend
– Declaración explícita de procesos
– Declaración implícita de procesos (ej: Ada)

Tema 11 Programación Concurrente y Sincronización 12


Declaración explícita de procesos

• Los procesos son unidades de programa


(como los procedimientos)
• Esto permite que la estructura del
programa sea más clara
• Nótese que esto no indica cuándo se
ejecutarán
• Ejemplos: Java, Ada, C/POSIX

Tema 11 Programación Concurrente y Sincronización 13


Hilos en Java

• Dos opciones:
– clase java.lang.Thread
• subclase de Object
• proporciona métodos constructores y un método run
– la secuencia principal de control del hilo
• forma de trabajo:
– crear una subclase de Thread y
– redefinir run
– interfaz Runnable:
public interface Runnable {
public abstract void run ();
}
• forma de trabajo:
– crear una clase que implemente Runnable y
– proporcione el método run

Tema 11 Programación Concurrente y Sincronización 14


La clase Thread de Java
public class Thread extends Object implements Runnable {
// Constructores
public Thread ();
public Thread (String nombre);
public Thread (Runnable objetivo);
public Thread (Runnable objetivo, String nombre);

public void run ();


public native synchronized void start ();
// lanza IllegalThreadStateException
public static Thread currentThread ();
public final void join () throws InterruptedException;
public final native boolean isAlive ();
public void destroy ();
// lanza SecurityException
public final void setDaemon ();
// lanza SecurityException
public final void isDaemon ();
...
}
Tema 11 Programación Concurrente y Sincronización 15
Ejemplo: brazo robot en Java
• Declaraciones previas:

public class EntradaUsuario {


public int nuevoAjuste (int eje) { ... }
...
}

public class Brazo {


public void mover (int eje, int pos) { ... }
...
}

EntradaUsuario IU = new EntradaUsuario();


Brazo Robot = new Brazo();
...

Tema 11 Programación Concurrente y Sincronización 16


Ejemplo: brazo robot en Java, usando la clase Thread. 1
public class Controlador extends Thread {

private int eje;

public Controlador (int Dimension) {


super();
eje = Dimension;
}

public void run () {


int posicion = 0;
int ajuste;

while (true) {
Robot.mover (eje, posicion);
ajuste = IU.nuevoAjuste (eje);
posicion += ajuste;
}
}
}
Tema 11 Programación Concurrente y Sincronización 17
Ejemplo: brazo robot en Java, usando la clase Thread. 2
final int ejeX = 0;
final int ejeY = 1;
final int ejeZ = 2;

Controlador hiloX = new Controlador (ejeX);


Controlador hiloY = new Controlador (ejeY);
Controlador hiloZ = new Controlador (ejeZ);

hiloX.start();
hiloY.start();
hiloZ.start();

...

Tema 11 Programación Concurrente y Sincronización 18


Ejemplo: brazo robot en Java, usando Runnable. 1
public class Controlador implements Runnable {

private int eje;

public Controlador (int Dimension) {


eje = Dimension;
}

public void run () {


int posicion = 0;
int ajuste;

while (true) {
Robot.mover (eje, posicion);
ajuste = IU.nuevoAjuste (eje);
posicion += ajuste;
}
}
}

Tema 11 Programación Concurrente y Sincronización 19


Ejemplo: brazo robot en Java, usando Runnable. 2
final int ejeX = 0;
final int ejeY = 1;
final int ejeZ = 2;

Controlador CX = new Controlador (ejeX);


Controlador CY = new Controlador (ejeY);
Controlador CZ = new Controlador (ejeZ);
// Todavía no hay hilos

// Asociar los controladores a hilos, e iniciarlos


Thread hiloX = new Thread (CX);
Thread hiloY = new Thread (CY);
Thread hiloZ = new Thread (CZ);

hiloX.start();
hiloY.start();
hiloZ.start();

...

Tema 11 Programación Concurrente y Sincronización 20


Hilos en C/POSIX

• POSIX soporta dos mecanismos: fork y


pthreads
• fork crea un nuevo proceso
• Las pthreads crean hilos
• Todos los hilos tienen atributos (ej: el
tamaño de la pila)
– Que se manipulan mediante objetos atributos
– Al crear un hilo, hay que pasarle el atributo
apropiado

Tema 11 Programación Concurrente y Sincronización 21


Interfaz C/POSIX.1c para hilos
typedef … pthread_t; /* tipo hilo de POSIX */
typedef … pthread_attr_t; /* tipo atributos de hilo */

int pthread_attr_init (pthread_attr_t *attr);


int pthread_attr_destroy (pthread_attr_t *attr);

int pthread_attr_setstacksize (…);


int pthread_attr_getstacksize (…);

int pthread_create (pthread_t *thread,


const_pthread_attr_t *attr,
void *(*start_routine) (void *),
void *arg);
int pthread_join (pthread_t thread, void **value_ptr);
int pthread_exit (void *value_ptr);

pthread_t pthread_self (void);

• Todas las funciones de tipo entero retornan un 0 si son


correctas, y un valor de error en otro caso

Tema 11 Programación Concurrente y Sincronización 22


Ejemplo: brazo robot en C/POSIX. 1
#include ″pthread_t″

typedef enum (ejeX, ejeY, ejeZ) dimension;

int nuevo_ajuste (int D);


void mueve_brazo (int D, int P);

pthread_attr_t atributos;
pthread_t hiloX, hiloY, hiloZ;

void controlador (dimension *eje) {


int posicion, ajuste;

posicion = 0;
while (1) {
ajuste = nuevo_ajuste (*eje);
posicion += ajuste;
mueve_brazo (+eje, posicion);
}
/* nota: no llamar a pthread_exit. El proceso no termina */
}

Tema 11 Programación Concurrente y Sincronización 23


Ejemplo: brazo robot en C/POSIX. 2
int main ( ) {
dimension X, Y, Z,
void *resultado; /* puntero al resultado */

X = ejeX;
Y = ejeY;
Z = ejeZ;

if (pthread_attr_init (&atributos) != 0)
exit (-1);
if (pthread_create (&hiloX, &atributos,
(void *) controlador, &X) = 0)
exit (-1);
if (pthread_create (&hiloY, &atributos,
(void *) controlador, &Y) = 0)
exit (-1);
if (pthread_create (&hiloZ, &atributos,
(void *) controlador, &Z) = 0)
exit (-1);
pthread_join (hiloX, (void **) &resultado); /* bloquear main */
}

Tema 11 Programación Concurrente y Sincronización 24


Sincronización
• El comportamiento correcto de un programa
concurrente
– formado por procesos que compiten y/o cooperan
– depende de la sincronización y comunicación entre
dichos procesos
• La comunicación es el paso de información entre
procesos
• La sincronización es la satisfacción de
restricciones en el orden en que se ejecutan
algunas de sus acciones
• Ambos conceptos están relacionados
• Formas de abordarlos:
– Datos compartidos
– Mensajes

Tema 11 Programación Concurrente y Sincronización 25


Exclusión mutua y Sincronización condicional
• Una secuencia de instrucciones que se debe
ejecutar de forma aparentemente indivisible se
denomina región crítica
– las operaciones de dos regiones críticas sobre unos
mismos datos no se entremezclan
– la forma de sincronización que se usa para proteger una
región crítica se llama exclusión mutua
• suponemos que el acceso a una celda individual de
memoria es atómico
• Cuando una acción de un proceso sólo se puede
realizar de forma segura si otros procesos están
en determinado estado o han ejecutado ciertas
acciones, se usa la sincronización condicional
• Ej: productor/consumidor con búfer limitado
– No se debe hacer Put cuando el búfer está lleno
– No se debe hacer Get cuando el búfer está vacío
– Además, hay exclusión mutua en el acceso al búfer

Tema 11 Programación Concurrente y Sincronización 26


Sincronización en C/POSIX.1c

• Soporta dos mecanismos que permiten


emular monitores con una interfaz de
procedimientos:
– Un cerrojo de exclusión mutua (mutex) es una
variable que proporciona exclusión mutua
mediante dos operaciones: lock y unlock
– Una variable de condición proporciona
sincronización condicional mediante dos
operaciones: wait y signal
• Ambos tipos de operaciones tienen
atributos asociados

Tema 11 Programación Concurrente y Sincronización 27


Interfaz C/POSIX.1c para sincronización entre hilos
typedef ... pthread_mutex_t;
typedef ... pthread_mutexattr_t;
typedef ... pthread_cond_t;
typedef ... pthread_condattr_t;

int pthread_mutex_init (pthread_mutex_t *mutex,


const pthread_mutexattr_t *attr);
int pthread_cond_init (pthread_cond_t *cond,
const pthread_condattr_t *attr);
int pthread_mutex_lock (pthread_mutex_t *mutex);
int pthread_mutex_unlock (pthread_mutex_t *mutex);
int pthread_cond_wait (pthread_cond_t *cond,
pthread_mutex_t *mutex);
int pthread_cond_signal (pthread_cond_t *cond);
int pthread_cond_broadcast (pthread_cond_t *cond,
pthread_mutex_t *mutex);

Tema 11 Programación Concurrente y Sincronización 28


Productor/consumidor con búfer limitado en C/POSIX. 1
#include <pthread.h>
#define TAM_BUFFER 20

typedef struct {
pthread_mutex_t mutex;
pthread_cond_t no_lleno;
pthread_cond_t no_vacio;
int datos[TAM_BUFFER];
int indLectura;
int indEscritura;
int cuenta;
} buffer_t;
...

buffer_t miBuffer;
...

miBuffer.cuenta = 0;
miBuffer.indLectura = 0;
miBuffer.indEscritura = 0;
...

Tema 11 Programación Concurrente y Sincronización 29


Productor/consumidor con búfer limitado en C/POSIX. 2
void pon (int dato, buffer_t *buffer) {
pthread_mutex_lock(&buffer->mutex);
while (buffer->cuenta == TAM_BUFFER)
pthread_cond_wait (&buffer->no_lleno, &buffer->mutex);
buffer->datos[buffer->indEscritura] = dato;
buffer->indEscritura = (buffer->indEscritura++)%TAM_BUFFER;
buffer->cuenta++;
pthread_cond_signal (&buffer->no_vacio);
pthread_mutex_unlock (&buffer->mutex);
}

int dame (buffer_t *buffer) {


int dato;
pthread_mutex_lock(&buffer->mutex);
while (buffer->cuenta == 0)
pthread_cond_wait (&buffer->no_vacio, &buffer->mutex);
dato = buffer->datos[buffer->indLectura];
buffer->indLectura = (buffer->indLectura++)%TAM_BUFFER;
buffer->cuenta--;
pthread_cond_signal (&buffer->no_lleno);
pthread_mutex_unlock (&buffer->mutex);
return dato;
}

Tema 11 Programación Concurrente y Sincronización 30


Sincronización en Java
• Exclusión mutua:
– en Java, cada objeto tiene asociado un bloqueo
• que no es accesible directamente desde el programa
• pero se utiliza de forma indirecta mediante:
– el modificador de método synchronized
» sólo se puede acceder al método si se ha obtenido el bloqueo
asociado con el objeto
» los métodos tiene acceso mutuamente exclusivo a los datos
encapsulados por el objeto
– la sincronización de bloque
• Sincronización condicional:
public void wait ();
• bloquea al hilo invocador y libera el bloqueo sobre el objeto
public void notify ();
• despierta a un hilo de los que esperan
• pero no libera el bloqueo
public void notifyAll ();
• despierta a todos los hilos que esperan
– los 3 métodos sólo se pueden llamar desde métodos que
mantienen el objeto bloqueado
• si no, lanzan la excepción IllegalMonitorStateException

Tema 11 Programación Concurrente y Sincronización 31


Exclusión mutua en Java: Entero Compartido
class EnteroCompartido {
private int elEntero;

public EnteroCompartido (int valorInicial) {


elEntero = valorInicial;
}

public synchronized int lee () {


return elEntero;
}

public synchronized void escribe (int valorNuevo) {


elEntero = valorNuevo;
}

public synchronized void incrementaEn (int cuanto) {


elEntero += cuanto;
}
}

Tema 11 Programación Concurrente y Sincronización 32


Productor/consumidor con búfer limitado en Java. 1
public class BufferLimitado {
private int datos[];
private int indLectura;
private int indEscritura;
private int cuenta;
private int tamBuffer;

public BufferLimitado (int tamanho) {


tamBuffer = tamanho;
datos = new int[tamBuffer);
cuenta = 0;
indEscritura = 0;
indLectura = 0;
}
...

Tema 11 Programación Concurrente y Sincronización 33


Productor/consumidor con búfer limitado en Java. 2
public synchronized void pon (int dato)
throws InterruptedException {
while (cuenta == tamBuffer)
wait ();
datos[indEscritura] = dato;
indEscritura = (indEscritura++)%tamBuffer;
cuenta++;
notify ();
}

public synchronized int dame () throws InterruptedException {


int dato;
while (cuenta == 0)
wait ();
dato = datos[indLectura];
indLectura = (indLectura++)%tamBuffer;
cuenta--;
notify();
return dato;
}
}

Tema 11 Programación Concurrente y Sincronización 34