Tema 6: Programación paralela de sistemas SMP

Anuncio
Departamento de Automática
Arquitectura e Ingeniería de Computadores
Tema 6
Programación paralela de sistemas SMP
Prof.
Dr. José Antonio de Frutos Redondo
Dr. Raúl Durán Díaz
Curso 2010-2011
Arquitectura e Ingeniería de Computadores
Programación de sistemas SMP
„
„
„
„
„
„
„
„
Introducción.
Hebras.
Sincronización.
Visibilidad de la memoria entre hebras.
Paradigmas de uso.
Programación avanzada.
Errores frecuentes.
Programación de un ejemplo.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
2
V1.3
1
Arquitectura e Ingeniería de Computadores
Introducción
„
Definición y terminología.
„
Concepto de hebra.
„
Comprende los elementos necesarios para ejecutar una sucesión
de instrucciones máquina:
„
„
Concepto de proceso (en Unix):
„
„
Contador de programa, los registros de datos y direcciones, etc.
Una o más hebras, espacio de direcciones, descriptores de
ficheros, etc.
Por lo tanto, un proceso puede estar ejecutando varias hebras
que compartirán el espacio de direcciones.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
3
V1.3
Arquitectura e Ingeniería de Computadores
Introducción
„
Definición y terminología.
„
Asincronía.
„
„
Dos operaciones son asíncronas cuando evolucionan con total
independencia una de otra.
Concurrencia.
„
Dos operaciones son concurrentes cuando se pueden
entremezclar arbitrariamente de forma que ambas progresen
independientemente.
„
„
Es decir: no se necesita que una acabe para comenzar la otra.
Paralelismo.
„
Se aplica a dos operaciones concurrentes que, además,
progresan simultáneamente.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
4
V1.3
2
Arquitectura e Ingeniería de Computadores
Introducción
„
Definición y terminología.
„
„
„
La concurrencia puede darse tanto en un sistema
monoprocesador como multiprocesador.
El paralelismo sólo puede darse en un sistema multiprocesador.
El modelo de hebras proporciona tanto concurrencia como
paralelismo, lo que lo hace útil en ambos tipos de máquinas.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
5
V1.3
Arquitectura e Ingeniería de Computadores
Introducción
„
Definición y terminología.
„
Código seguro en el modelo de hebra.
„
Llamamos código seguro al que puede ser ejecutado
simultáneamente por varias hebras sin resultados destructivos.
„
„
Atención: no es lo mismo “seguro” que “eficiente”.
Podemos convertir una función (que no tenga estado) en segura
utilizando un bloqueo al llamarla que liberamos al terminar.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
6
V1.3
3
Arquitectura e Ingeniería de Computadores
Introducción
„
Definición y terminología.
„
Hebra re-entrante.
„
Se trata de un modelo “eficiente” de código seguro. Para ello:
„
„
„
evitar datos estáticos (es decir, persistentes de una llamada a otra);
independiente de cualquier forma de sincronización entre las hebras.
Cuando es necesario guardar el estado, es mejor hacerlo sobre
estructuras de datos externas a la función que puede controlar y
sincronizar el llamador.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
7
V1.3
Arquitectura e Ingeniería de Computadores
Introducción
„
Definición y terminología.
„
„
Protección de código frente a protección de datos.
Conceptualmente es mejor proteger los datos que el código.
Tomemos la función putchar como ejemplo:
„
Protegemos su ejecución mediante un bloqueo (mutex):
„
„
„
Ventaja: garantizamos que sólo una hebra puede escribir un carácter
cada vez.
Problema: si una hebra quiere escribir en otro canal, ¡no puede
hacerlo!
Solución: proteger el canal (es decir, su estructura de datos
asociada) y no el código.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
8
V1.3
4
Arquitectura e Ingeniería de Computadores
Introducción
„
Costes de la programación multihebra.
„
Sobrecarga de computación.
„
„
La sincronización tiene coste computacional: hay que mantenerla
al mínimo.
Deficiencias a niveles más bajos. Si se usan hebras para
paralelizar la I/O, pueden aparecer cuellos de botella en:
„
„
„
„
„
Librería ANSI C.
Sistema operativo.
Sistema de ficheros.
Drivers de los dispositivos.
En procesos de computación intensiva, adecuar el número de
hebras al de procesadores (fase de mapeo).
„
Más hebras que procesadores suele dar peor rendimiento.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
9
V1.3
Arquitectura e Ingeniería de Computadores
Introducción
„
Costes de la programación multihebra.
„
Autodisciplina del programador.
„
„
„
„
La programación con hebras es más difícil.
Hay que ser cautos con el código desarrollado por terceros.
¡No olvidar nunca que cualquier hebra ve todo el espacio de
direcciones de las demás!
Dificultad de depuración.
„
„
Faltan herramientas.
Los errores son sutiles:
„
„
La temporización es clave y puede hacer aparecer o desaparecer
errores.
La corrupción de punteros es muy difícil de rastrear.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
10
V1.3
5
Arquitectura e Ingeniería de Computadores
Introducción
„
Normativa POSIX.
„
„
Seguimos la norma POSIX 1003.1c – 1995.
El nombre pthreads se refiere a POSIX threads.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
11
V1.3
Arquitectura e Ingeniería de Computadores
Hebras
„
Creación y uso.
„
„
Creación y gestión de hebras.
Objeto básico:
pthread_t thread;
„
Funciones relacionadas:
int pthread_create(pthread_t *thread, const pthread_attr_t
*attr, void *(*start)(void *), void *arg);
int pthread_exit(void *value_ptr);
int pthread_detach(pthread_t thread);
int pthread_join(pthread_t thread, void **value_ptr);
int sched_yield(void);
int pthread_equal(pthread_t t1, pthread_t t2);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
12
V1.3
6
Arquitectura e Ingeniería de Computadores
Hebras
„
La hebra se representa con el tipo de dato pthread_t.
„
La ejecución de la hebra comienza llamando a la función que
se indique en start, a la que se le pasará el argumento arg.
El identificador de la hebra creada se almacena en la variable
tipo pthread_t.
El identificador es necesario para actuar sobre la hebra. Una
hebra puede obtener su propio identificador con
pthread_self(). No hay forma de obtener el de otra, salvo
que se haya guardado previamente.
La función pthread_equal() permite comparar dos
identificadores.
„
„
„
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
13
V1.3
Arquitectura e Ingeniería de Computadores
Hebras
„
El main es la hebra inicial.
„
Si se termina, se termina todo el proceso (en el sentido del
sistema operativo):
„
„
„
„
„
obliga a las demás hebras a terminar;
devuelve al sistema operativo los recursos de todo el proceso.
Por lo demás se comporta como una hebra normal.
Todas las hebras creadas comparten el espacio de
direcciones y los descriptores de ficheros.
A cada hebra se le asigna una pila propia de tamaño fijo,
especificable, mediante el parámetro de atributos, en el
momento de creación.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
14
V1.3
7
Arquitectura e Ingeniería de Computadores
Hebras
„
La función pthread_exit() termina una hebra.
„
Una hebra terminada sólo devuelve sus recursos al sistema
operativo si está en estado detached.
Para poner una hebra en estado detached se utiliza la función
pthread_detach().
El estado detached no afecta a la hebra: sólo informa al
sistema operativo de que sus recursos se pueden recuperar
cuando ésta termine.
„
„
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
15
V1.3
Arquitectura e Ingeniería de Computadores
Hebras
„
Ciclo de vida:
desbloqueada
Lista
Bloqueada
detenida
creada
planificada
esperando recurso
Corriendo
terminada o cancelada
Terminada
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
16
V1.3
8
Arquitectura e Ingeniería de Computadores
Hebras
„
Una hebra puede devolver un valor al terminar, si otra hebra
llama a la función pthread_join().
„
Recordemos que la sintaxis es:
int pthread_join(pthread_t thread, void **value_ptr);
„
Si no nos interesa el valor retornado, podemos pasar NULL
en lugar de value_ptr.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
17
V1.3
Arquitectura e Ingeniería de Computadores
Relación entre hebras y kernel
„
Entre las hebras y el procesador, suele haber un nivel de
abstracción intermedio, como, por ejemplo:
„
„
„
„
„
Un proceso Unix tradicional,
Una hebra de kernel,
Un light-weight process, como en Solaris, etc.
Estas “entidades de kernel” están gestionadas por el sistema
operativo.
Se relaciona con las hebras a través de algún subsistema
que llamamos planificador.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
18
V1.3
9
Arquitectura e Ingeniería de Computadores
Relación entre hebras y kernel
„
Las hebras se relacionan con las “entidades” del kernel como
en esta figura:
Hebras (creadas por el usuario)
Planificador
El usuario puede ejercer cierto
control sobre la planificación.
Entidades
del kernel
(gestionadas
por el SO)
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
19
V1.3
Arquitectura e Ingeniería de Computadores
Ejemplo de programación con fork
„
Programación asíncrona. Ejemplo base:
# include “todo.h”
# define
TRUE 1
int main(int argc, char *argv[]) {
int segundos
= 0;
char line[128]
= “”;
char mensaje[64] = “”;
while(TRUE) {
printf(“Alarma> “);
if (fgets(line, sizeof(line), stdin) == NULL) exit(0);
if (strlen(line) <= 1) continue;
if (sscanf(line, “%d %64[^\n]”, &segundos, mensaje) < 2)
fprintf(stderr, “Comando erróneo.\n”);
else {
sleep(segundos);
printf(“(%d) ‘%s’\n”, segundos, mensaje);
}
}
exit(0);
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
20
V1.3
10
Arquitectura e Ingeniería de Computadores
Ejemplo de programación con fork
Ejemplo de programación asíncrona creando procesos:
„
...
else {
pid = fork();
if (pid == 0) {
sleep(segundos);
printf(“(%d) ‘%s’\n”, segundos, mensaje);
exit(0);
}
else {
do {
pid = waitpid(-1, NULL, WNOHANG);
} while (pid != 0);
}
}
}
exit(0);
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
21
V1.3
Arquitectura e Ingeniería de Computadores
Ejemplo de programación con hebras
„
Ejemplo de programación asíncrona usando hebras:
# include <pthread.h>
typedef struct {
int segundos;
char mensaje[64];
} alarm_t;
...
alarm_t
*alarm;
pthread_t thread;
...
alarm = (alarm_t *)malloc(sizeof(alarm_t));
...
else {
pthread_create(&thread, NULL, alarm_thread, alarm);
}
}
exit(0);
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
22
V1.3
11
Arquitectura e Ingeniería de Computadores
Ejemplo de programación con hebras
„
Ejemplo de programación asíncrona usando hebras:
# include <pthread.h>
void *alarm_thread(void *arg) {
alarm_t *alarm = (alarm_t *)arg;
pthread_detach(pthread_self());
sleep(alarm->segundos);
printf(“(%d) ‘%s’\n”, segundos, mensaje);
free(alarm);
return NULL;
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
23
V1.3
Arquitectura e Ingeniería de Computadores
Sincronización
„
„
Con frecuencia existen unas relaciones implícitas entre
diversas variables de un programa.
Ejemplo:
„
una cola de elementos con cabecera
„
„
„
la cabecera apunta al primer elemento o es NULL.
cada elemento apunta al siguiente o a NULL si es el último.
Si estas relaciones no se respetan, el programa fallará o dará
resultados erróneos.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
24
V1.3
12
Arquitectura e Ingeniería de Computadores
Sincronización
„
Secciones críticas.
„
„
„
Las relaciones entre variables se pueden “infringir”
temporalmente con tal de que el resto del código no pueda
“enterarse” de ello.
„
„
„
„
Áreas de código que afectan a datos compartidos.
Pueden ser vistas también como invariantes de datos.
Por ejemplo, el estado de la cola no debe alterarse cuando se
inserta o se elimina un elemento.
Cuando hablamos de sincronización nos referimos a la
protección del programa frente a la alteración de las
relaciones.
La sincronización es un proceso cooperativo.
Los predicados son expresiones lógicas que describen el
estado de las relaciones.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
25
V1.3
Arquitectura e Ingeniería de Computadores
Sincronización
„
Mutexes (semáforos de exclusión mutua).
„
Para sincronizar las hebras hay que asegurar el acceso
mutuamente excluyente a los datos compartidos.
„
„
La sincronización es importante para:
„
„
„
Sólo una hebra accede cada vez.
modificar datos;
para leer datos previamente escritos si el orden es importante.
¡Atención! Con frecuencia, el hardware no nos garantiza un
orden predecible en el acceso a memoria.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
26
V1.3
13
Arquitectura e Ingeniería de Computadores
Sincronización
„
„
Creación y destrucción de mutexes.
Objeto básico:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
„
Funciones relacionadas:
„
Creación:
int pthread_mutex_init(pthread_mutex_t *mutex,
pthread_mutexattr_t *attr);
„
„
Esta función sirve para crear un mutex dinámicamente.
Destrucción:
int ptread_mutex_destroy(pthread_mutex_t *mutex);
„
Un mutex creado dinámicamente se puede destruir una vez se
tenga certeza de que no hay hebras que lo retengan bloqueado.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
27
V1.3
Arquitectura e Ingeniería de Computadores
Sincronización
„
Bloqueo y desbloqueo de mutexes:
int pthread_mutex_lock(pthread_mutex_t *mutex);
„
„
Esta función espera hasta conseguir el bloqueo del mutex.
¡Atención! No bloquear un mutex que la misma hebra ya tenía
bloqueado previamente.
int pthread_mutex_unlock(pthread_mutex_t *mutex);
„
Una hebra sólo puede desbloquear los mutexes que le pertenecen,
es decir, los que ella bloqueó.
int pthread_mutex_trylock(pthread_mutex_t *mutex);
„
Esta función devuelve el error EBUSY si el mutex ya estaba
bloqueado. Puede servir para evitar los “abrazos mortales”.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
28
V1.3
14
Arquitectura e Ingeniería de Computadores
MUTEX (MUTual EXclusion)
Secuencia de operaciones con mutex
Hebra 2 espera
Hebra 1 bloquea
Hebra 1
Hebra 3 espera
Hebra 1 intenta
bloquear
Retorno EBUSY
Hebra 2
Hebra 3
mutex
tiempo
Hebra 1 bloquea
Hebra 1 desbloquea
Hebra 2 bloquea
© J. A. de Frutos Redondo, R. Durán 2006
Hebra 2 desbloquea
6. Programación paralela de sistemas SMP
29
V1.3
Arquitectura e Ingeniería de Computadores
Sincronización
„
Variables de estado.
„
Se utilizan para comunicar el estado de los datos compartidos.
„
„
„
„
„
Una variable de estado siempre lleva asociado un mutex.
La hebra bloquea el mutex y después espera cambio de estado.
El sistema pthreads asegura atomicidad en las operaciones
espera cambio de estado y desbloqueo de mutex.
Las variables de estado sirven para señalizar una condición: no
proporcionan exclusión mutua.
„
„
Ejemplo: la hebra que atiende una cola puede señalizar que ésta
está vacía para que otra hebra actúe apropiadamente.
Por eso se utilizan en conjunción con los mutexes.
Cada variable de estado lleva asociado uno y solo un
predicado.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
30
V1.3
15
Arquitectura e Ingeniería de Computadores
Sincronización
„
„
Creación y destrucción de variables de estado.
Objeto básico:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
„
Funciones relacionadas:
„
Creación:
int pthread_cond_init(pthread_cond_t *cond,
pthread_condattr_t *attr);
„
„
Crea una variable de estado dinámicamente.
Destrucción:
int ptread_cond_destroy(pthread_cond_t *cond);
„
Una variable de estado creada dinámicamente se puede destruir
una vez se tenga certeza de que no hay ni habrá hebras a la
espera de cambio de estado, ni ninguna señalizará ningún cambio
de estado.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
31
V1.3
Arquitectura e Ingeniería de Computadores
Sincronización
„
Funciones de espera:
„
Espera ordinaria:
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
„
„
„
„
Cada variable de estado debe estar asociada con uno y sólo un
mutex.
Cuando la hebra invoca esta función, ha de tener bloqueado el
mutex.
La operación de espera liberará el mutex justo al comenzar la
espera.
Una vez señalizada la condición, la función se desbloquea y
retoma el bloqueo del mutex antes de seguir adelante.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
32
V1.3
16
Arquitectura e Ingeniería de Computadores
Sincronización
„
Funciones de espera:
„
Espera con plazo de expiración:
int ptread_cond_timedwait(pthread_cond_t *cond,
pthread_mutex_t *mutex,
struct timespec *expiration);
„
La función puede retornar bien por señalización de la condición
bien por expiración del plazo.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
33
V1.3
Arquitectura e Ingeniería de Computadores
Sincronización
„
Puntos importantes:
„
Chequear siempre el predicado.
„
„
„
después de obtener el bloqueo del mutex y antes de llamar a la
función de espera;
al retornar de la función de espera.
Lo mejor es esperar dentro de un lazo el cambio de estado.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
34
V1.3
17
Arquitectura e Ingeniería de Computadores
Sincronización
„
Señalización de cambio de estado:
„
Señalizar a una sola hebra:
int ptread_cond_signal(pthread_cond_t *cond);
„
„
„
Necesitamos señalizar a sólo una hebra el cambio de estado.
Cualquiera de las hebras en espera puede procesar ese cambio.
Señalizar a todas las hebras simultáneamente:
int ptread_cond_broadcast(pthread_cond_t *cond);
„
No es imprescindible bloquear el mutex asociado a una variable
de estado antes de señalizarla. Puede ser más eficiente en
muchos sistemas.
„
Pero si entre la señalización y el retorno otra hebra adquiere el
mutex, la hebra señalizada ha de esperar que ésta lo libere. Puede
darse así la inversión de prioridad.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
35
V1.3
Arquitectura e Ingeniería de Computadores
VARIABLES DE CONDICIÓN
Hebra 1
Hebra 2
Hebra 3
Variable de
condición
Señal de hebra 1 sin
hebras esperando
Hebra 1 espera
Hebra 2 espera
Señal de hebra 3
despierta hebra1
Hebra 3 espera
© J. A. de Frutos Redondo, R. Durán 2006
Tiempo de espera
de hebra 3 superado
Hebra 3 espera con
limite de tiempo
Hebra 1 broadcast
Despiertan 2 y 3
6. Programación paralela de sistemas SMP
36
V1.3
18
Arquitectura e Ingeniería de Computadores
Visibilidad de la memoria entre hebras
„
La norma implica seguir estas reglas:
„
„
„
„
Una hebra recién creada ve en la memoria los mismos valores
que la hebra que la creó.
Una hebra que adquiere un mutex ve en la memoria los mismos
valores que la hebra que previamente liberó el mutex.
Una hebra que hace join con otra que termina ve en memoria
los mismos valores que ésta.
Una hebra que es señalizada ve en memoria los mismos
valores que la hebra señalizadora.
„
„
Cualquier dato escrito después de alguna de las acciones
precedentes no se garantiza que sea visto por la hebra afectada.
No se puede establecer supuestos respecto al orden en que los
datos se escribirán en memoria.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
37
V1.3
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Existen muchas soluciones para estructurar una solución de
tipo hebra. Los paradigmas básicos son:
„
„
„
„
Modelo trabajo en cadena.
Modelo grupo de trabajo.
Modelo cliente-servidor.
Combinación de modelos.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
38
V1.3
19
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Modelo trabajo en cadena.
„
„
El trabajo se descompone en una serie de tareas secuenciales,
con una entrada y una salida claramente definidas.
Cada hebra realiza una o más de estas tareas secuenciales
sobre conjuntos de datos sucesivos, pasando el resultado a otra
hebra para realice el siguiente paso.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
39
V1.3
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Modelo trabajo en cadena:
Entrada
Hebra A
Hebra B
Hebra C
Salida
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
40
V1.3
20
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Modelo grupo de trabajo.
„
„
„
„
El trabajo se descompone en tareas paralelas.
Se descompone un conjunto de datos en subconjuntos, cada
uno de los cuales será procesado por una hebra.
El concepto es en parte similar a una máquina tipo SIMD.
Un grupo de trabajo es un conjunto de hebras que realizan
procesamiento independiente sobre conjuntos distintos de
datos. En este sentido se puede comparar más a un MIMD.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
41
V1.3
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Modelo grupo de trabajo:
Entrada
Hebra A
Hebra B
Hebra C
Salida
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
42
V1.3
21
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Modelo cliente-servidor.
„
„
„
El servidor (o servidores) realiza una determinada tarea
especializada.
El cliente pide al servidor la realización de ese trabajo y,
generalmente, procede a realizar otras tareas, por ejemplo,
solicitar otros trabajos a otros servidores.
El cliente se limita así a coordinar las tareas entre distintos
servidores. Por eso este modelo se conoce también como
“capataz-trabajadores”.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
43
V1.3
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Modelo cliente-servidor:
Entrada A
Entrada B
Entrada C
Servidor
Salida A
© J. A. de Frutos Redondo, R. Durán 2006
Salida B
Salida C
6. Programación paralela de sistemas SMP
44
V1.3
22
Arquitectura e Ingeniería de Computadores
Paradigmas de uso
„
Combinación de modelos.
„
„
Los modelos se pueden combinar de cualquier modo
imaginable, para adaptarse a las necesidades.
Ejemplos:
„
„
Una fase de un modelo trabajo en cadena se puede implementar
usando cliente-servidor.
Un servidor puede estar implementado usando trabajo en cadena.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
45
V1.3
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
„
Inicialización única.
Objetos atributo.
„
„
„
„
Atributos de mutex.
Atributos de variable de estado.
Atributos de hebra.
Cancelación.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
46
V1.3
23
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Inicialización única.
„
„
„
„
„
Se utiliza cuando hay alguna o algunas operaciones que han de
ser realizadas solamente una vez por no importa qué hebra.
Ejemplos: inicialización de variables, creación de mutexes,
creación de datos específicos de hebra, etc.
Normalmente usaremos una variable booleana para controlar,
protegida por un mutex inicializado estáticamente.
En un programa estas operaciones se realizan típicamente en
el main.
Si esto no es posible, (por ejemplo, en una librería) usamos la
función pthread_once.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
47
V1.3
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Objeto básico:
pthread_once_t once_control = PTHREAD_ONCE_INIT;
„
Funciones relacionadas:
int pthread_once(pthread_once_t *once_control,
void (*rutina_inicio)(void));
„
„
„
La función rutina_inicio será ejecutada sólo una vez por no
importa qué hebra.
La pthread_once comprueba primero la variable de control
once_control, para determinar si se ha completado ya la
inicialización.
Al terminar, la función llamante tiene garantizada la inicialización.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
48
V1.3
24
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Objetos atributo.
„
„
„
Un objeto atributo es una lista extendida de argumentos que se
añade cuando se van a crear ciertos objetos.
Permite que la creación de esos objetos sea “sencilla”, para el
neófito, pero flexible para que un “experto” pueda sacar todo el
partido.
Se pueden aplicar a:
„
„
„
„
Atributos de mutex.
Atributos de variable de estado.
Atributos de hebra.
No todas las implementaciones soportan todos los atributos.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
49
V1.3
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Atributos de mutex.
„
Objeto básico:
pthread_mutexattr_t attr;
„
Creación:
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
„
Destrucción:
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
„
Atributo soportado: compartición del mutex entre procesos.
int pthread_mutexattr_getpshared(pthread_mutexattr_t *attr,
int *pshared);
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,
int pshared);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
50
V1.3
25
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Atributos de variable de condición.
„
Objeto básico:
pthread_condattr_t attr;
„
Creación:
int pthread_condattr_init(pthread_condattr_t *attr);
„
Destrucción:
int pthread_condattr_destroy(pthread_condattr_t *attr);
„
Atributo soportado: compartición de la variable de condición
entre procesos.
int pthread_condattr_getpshared(pthread_condattr_t *attr,
int *pshared);
int pthread_condattr_setpshared(pthread_condattr_t *attr,
int pshared);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
51
V1.3
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Atributos de hebra.
„
Objeto básico:
pthread_attr_t attr;
„
Creación:
int pthread_attr_init(pthread_attr_t *attr);
„
Destrucción:
int pthread_attr_destroy(pthread_attr_t *attr);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
52
V1.3
26
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Atributos soportados para hebra.
„
Detach state:
„
Si el identificador de hebra puede usarse para hacer JOIN, se trata
de una hebra JOINABLE.
„
„
„
„
El valor del atributo será PTHREAD_CREATE_JOINABLE.
Si cuando la hebra termina, puede devolver inmediatamente todos
sus recursos al sistema operativo, la hebra es DETACHED.
El valor del atributo será PTHREAD_CREATE_DETACHED.
Por defecto las hebras se crean en modo JOINABLE.
int pthread_attr_getdetachstate(pthread_attr_t *attr,
int *detachstate);
int pthread_attr_setdetachstate(pthread_attr_t *attr,
int detachstate);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
53
V1.3
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Atributos soportados para hebra.
„
Tamaño de pila:
„
En algunos casos puede ser interesante modificar la cantidad de
memoria reservada para la pila.
„
„
Ejemplo: si la jerarquía de llamadas a funciones baja demasiados
niveles.
Atención: cambiar el tamaño de la pila hace el código poco portátil.
int pthread_attr_getstacksize(pthread_attr_t *attr,
size_t *stacksize);
int pthread_attr_setstacksize(pthread_attr_t *attr,
size_t stacksize);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
54
V1.3
27
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Cancelación.
„
Las hebras suelen terminar por sí mismas, pero a veces es
interesante cancelarlas. Ejemplos:
„
„
„
Cancelar una hebra no la mata automáticamente: depende de
su estado de cancelación.
„
„
El usuario presiona el botón “Cancelar”.
Una hebra es parte de un algoritmo redundante en que otra hebra
ya ha obtenido resultados válidos.
Es, más bien, pedirle de forma “educada” que termine.
Necesitamos su identificación:
int pthread_cancel(pthread_t thread);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
55
V1.3
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Cancelación.
„
Se codifica mediante dos bits:
„
Bit de estado:
„
„
„
Bit de tipo:
„
„
„
enable
disable
deferred
asynchronous
Por defecto, la cancelación es deferred. Significa que sólo
puede ocurrir en ciertos puntos del programa, en que la propia
hebra comprueba si se le ha pedido terminar.
„
Ejemplo: al esperar el cambio de una variable de condición, al leer
o escribir un fichero, etc. Se puede pedir explícitamente la
comprobación mediante pthread_testcancel(void);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
56
V1.3
28
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Cancelación.
„
„
En el tipo asíncrono indicamos que la hebra puede finalizar
inmediatamente, sin ulteriores comprobaciones.
Funciones de cambio de tipo y estado:
int pthread_setcancelstate(int state, int *oldstate);
„
Los valores de state pueden ser:
„
„
PTHREAD_CANCEL_ENABLE
PTHREAD_CANCEL_DISABLE
int pthread_setcanceltype(int type, int *oldtype);
„
Los valores de type pueden ser:
„
„
PTHREAD_CANCEL_DEFERRED
PTHREAD_CANCEL_ASYNCHRONOUS
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
57
V1.3
Arquitectura e Ingeniería de Computadores
Programación avanzada
„
Cancelación.
„
Funciones para insertar una rutina de limpieza antes de que se
ejecute la cancelación:
int pthread_cleanup_push(void (*rutina)(void *), void *arg);
„
Envía la rutina rutina a la pila de handlers de limpieza. Será
ejecutada si:
Si es cancelada la hebra.
Si la hebra ejecuta pthread_exit
„ Si la hebra ejecuta pthread_cleanup_pop con un valor de
argumento distinto de cero.
int pthread_cleanup_pop(int execute);
„
„
„
Saca de la pila el handler introducido por una llamada a la
pthread_cleanup_push. Además lo ejecuta si el argumento es
distinto de cero.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
58
V1.3
29
Arquitectura e Ingeniería de Computadores
Errores frecuentes
„
„
„
„
„
„
“Inercia” de las hebras.
Competición entre las hebras.
Evitar abrazos mortales.
Inversión de prioridad.
Compartición de variables de estado en diferentes
predicados.
Compartición de variables privadas.
„
„
Pila
Problemas de rendimiento.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
59
V1.3
Arquitectura e Ingeniería de Computadores
Errores frecuentes
„
“Inercia” de las hebras.
„
„
„
„
„
Recordar siempre que las hebras son asíncronas.
En un sistema monoprocesador, una hebra que crea otra tiene
una cierta ventaja sobre ella en cuanto al momento del
comienzo de ejecución: hay un “ligero sincronismo”.
También puede pasar lo mismo en un multiprocesador si se ha
alcanzado el límite de procesadores disponibles.
Todo esto crea la ilusión de la “inercia”.
Sin embargo, nunca se debe basar el código en suponer que
una hebra recién creada “tarda” en arrancar.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
60
V1.3
30
Arquitectura e Ingeniería de Computadores
Errores frecuentes
„
Competición entre las hebras.
„
„
„
„
La competición se da cuando dos o más hebras quieren llegar o
hacer lo mismo a la vez.
Sólo una de ellas gana la competición.
Cuál gana viene dado por muchos factores, muchos de ellos
fuera de control.
No olvidar que:
„
„
„
una hebra puede ser interrumpida en cualquier punto arbitrario por
un plazo indefinido de tiempo;
no existe más orden entre ellas que el causado por el
programador;
planificación no es lo mismo que sincronización.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
61
V1.3
Arquitectura e Ingeniería de Computadores
Errores frecuentes
„
Evitar abrazos mortales
„
„
„
Los abrazos mortales surgen por sincronización errónea.
La hebra A tiene el recurso 1 y no puede seguir hasta tener el
recurso 2; la hebra B tiene el recurso 2 y no puede seguir hasta
tener el recurso 1.
Los recursos más conflictivos suelen ser los mutexes.
„
Se debe evitar bloquear más de un mutex simultáneamente.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
62
V1.3
31
Arquitectura e Ingeniería de Computadores
Errores frecuentes
„
Inversión de prioridad
„
„
„
Este problema suele estar asociado a las aplicaciones de
tiempo real.
Ocurre cuando una hebra de alta prioridad es bloqueada en su
ejecución por otra hebra de baja prioridad.
Ejemplo:
„
„
„
„
una hebra de baja prioridad bloquea un mutex;
es interrumpida por una hebra de alta prioridad que quiere
bloquear el mismo mutex y, por lo tanto, queda bloqueada;
una tercera hebra de prioridad media impide la ejecución –y
desbloqueo del mutex– de la hebra de baja prioridad;
Resultado: la hebra de alta prioridad queda indefinidamente
bloqueada.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
63
V1.3
Arquitectura e Ingeniería de Computadores
Errores frecuentes
„
Compartición de variables de estado en diferentes
predicados.
„
Cada variable debe usarse para comprobar una y sólo una
condición.
„
„
Si no se hace así, puede que la señalización de la condición
despierte a la hebra que no esperaba por ella... y el programa se
para.
Compartición de variables privadas.
„
Pila
„
„
Se puede hacer siempre que se garantice que la función a la que
pertenece la pila compartida no hace “return” mientras el resto de
hebras usuarias no hayan terminado de hacer uso de ella.
¡Atención a los punteros no inicializados!
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
64
V1.3
32
Arquitectura e Ingeniería de Computadores
Errores frecuentes
„
Problemas de rendimiento.
„
„
„
„
Atención al paralelismo “serializado”.
Usar un sólo “gran” mutex para acceder a una librería es un
paralelismo muy grosero...
... pero usar demasiados mutexes penaliza fuertemente el
rendimiento.
Para evitar contención con el cache, es bueno alinear y separar
datos usados por diferentes hebras y que afecten
sustancialmente al rendimiento.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
65
V1.3
Arquitectura e Ingeniería de Computadores
Programa ejemplo
„
„
„
Presentamos una aplicación para obtener la lista de números
primos hasta una cierta cantidad.
Utilizamos el paradigma de grupo de trabajo.
Hacemos uso de mutexes, variables de estado, predicados,
estados de cancelación, etc.
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
66
V1.3
33
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/* Ejemplo de búsqueda de números primos */
#include
#include
#include
#include
<pthread.h>
<stdio.h>
<stdlib.h>
<errno.h>
/* Constantes usadas */
#define workers 5
/* Hebras que realizan la búsqueda */
#define request 110 /* Primos a encontrar */
/*
* Macros
*/
#define check(status,string) if (status != 0) { \
errno = status; \
fprintf(stderr, "%s status %d: %s\n", \
string, status, strerror(status)); \
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
67
V1.3
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/* Datos globales */
pthread_mutex_t
prime_list = PTHREAD_MUTEX_INITIALIZER;
/* Mutex para el primo */
pthread_mutex_t
current_mutex = PTHREAD_MUTEX_INITIALIZER; /* Número actual */
pthread_mutex_t
cond_mutex = PTHREAD_MUTEX_INITIALIZER;
/* Mutex para arranque */
pthread_cond_t
cond_var = PTHREAD_COND_INITIALIZER; /* Variable de estado para el arranque */
int current_num = 1;
int thread_hold = 1;
int count
= 1;
int primes[request];
pthread_t threads[workers];
/*
/*
/*
/*
/*
Siguiente número a comprobar */
Número asociado al estado */
Cuenta de números primos e índice a los mismos */
Reserva de primos – acceso sincronizado */
Matriz con las hebras trabajadoras */
static void unlock_cond(void *arg)
{
int status;
status = pthread_mutex_unlock(&cond_mutex);
check(status, "Mutex_unlock");
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
68
V1.3
34
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/*
Rutina de trabajo.
Cada hebra arranca con esta rutina. Se realiza primero una espera diseñada
para sincronizar los trabajadores con el capataz. Cada trabajador hace después
su turno tomando un número del que determina si es primo o no.
*/
void *prime_search(void *arg)
{
int numerator;
/* Usada para determinar la primalidad */
int denominator;
/* Usada para determinar la primalidad */
int cut_off;
/* Número a comprobar dividido por 2 */
int notifiee;
/* Usada durante la cancelación */
int prime;
/* Flag para indicar la primalidad */
int my_number;
/* Identificador de la hebra trabajadora */
int status;
/* Status de las llamadas a pthread_* */
int not_done = 1; /* Predicado del lazo de trabajo */
int oldstate;
/* Estado de cancelado previo */
my_number = (int)arg;
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
69
V1.3
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/*
Sincronizamos los trabajadores y el capataz usando una variable de estado cuyo
predicado (thread_hold) será rellenado por el capataz.
*/
status = pthread_mutex_lock(&cond_mutex);
check(status, "Mutex_lock");
pthread_cleanup_push(unlock_cond, NULL);
while (thread_hold) {
status = pthread_cond_wait(&cond_var, &cond_mutex);
check(status, "Cond_wait");
}
pthread_cleanup_pop(1);
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
70
V1.3
35
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/*
Realiza las comprobaciones sobre números cada vez mayores hasta encontrar el
número deseado de primos.
*/
while (not_done) {
pthread_testcancel();
/* Comprobar petición de cancelación */
/* Obtener siguiente número a comprobar */
status = pthread_mutex_lock(&current_mutex);
check(status, "Mutex_lock");
current_num = current_num + 2; /* Nos saltamos los pares */
numerator = current_num;
status = pthread_mutex_unlock(&current_mutex);
check(status, "Mutex_unlock");
/* Verificamos primalidad hasta número/2 */
cut_off = numerator/2 + 1;
prime = 1;
/* Comprobamos la divisibilidad */
for (denominator = 2;((denominator < cut_off) && (prime)); denominator++)
prime = numerator % denominator;
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
71
V1.3
Arquitectura e Ingeniería de Computadores
Programa ejemplo
if (prime != 0) {
/* Inhibir posibles cancelaciones */
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate);
/*
Obtener mutex y añadir este primo a la lista. Cancelar el resto de hebras si
ya se ha obtenido la cantidad pedida de primos.
*/
status = pthread_mutex_lock(&prime_list);
check(status, "Mutex_lock");
if (count < request) {
primes[count] = numerator;
count++;
}
else if (count == request) {
not_done = 0;
count++;
for (notifiee = 0; notifiee < workers; notifiee++) {
if (notifiee != my_number) {
status = pthread_cancel(threads[notifiee]);
check(status, "Cancel");
}
}
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
72
V1.3
36
Arquitectura e Ingeniería de Computadores
Programa ejemplo
status = pthread_mutex_unlock(&prime_list);
check (status, "Mutex_unlock");
/* Permitir de nuevo cancelaciones */
pthread_setcancelstate(oldstate, &oldstate);
}
pthread_testcancel();
}
return arg;
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
73
V1.3
Arquitectura e Ingeniería de Computadores
Programa ejemplo
main()
{
int worker_num;
/* Índice de trabajadores */
void *exit_value;
/* Estado final para cada trabajador */
int list;
/* Para imprimir la lista de primos encontrados */
int status;
/* Status de las llamadas a pthread_* */
int index1;
/* Para ordenar los primos */
int index2;
/* Para ordenar los primos */
int temp;
/* Parte de la ordenación */
int line_idx;
/* Alineado de columna en salida */
/*
Creación de las hebras trabajadoras.
*/
for (worker_num = 0; worker_num < workers; worker_num++) {
status = pthread_create(&threads[worker_num], NULL,
prime_search, (void *)worker_num);
check(status, "Pthread_create");
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
74
V1.3
37
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/*
Poner a cero el predicado thread_hold y señalizar globalmente que los
trabajadores pueden comenzar.
*/
status = pthread_mutex_lock(&cond_mutex);
check(status, "Mutex_lock");
thread_hold = 0;
status = pthread_cond_broadcast(&cond_var);
check(status, "Cond_broadcast");
status = pthread_mutex_unlock(&cond_mutex);
check(status, "Mutex_unlock");
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
75
V1.3
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/*
Hacer JOIN con cada trabajador para obtener los resultados y asegurarse de que
todos se han completado correctamente.
*/
for (worker_num = 0; worker_num < workers; worker_num++) {
status = pthread_join(threads[worker_num], &exit_value);
check(status, "Pthread_join");
/*
Si la terminación es correcta, el valor final exit_value es igual a
worker_num.
*/
if (exit_value == (void *)worker_num)
printf(“Hebra %d terminada normalmente.\n", worker_num);
else if (exit_value == PTHREAD_CANCELED)
printf(“Hebra %d fue cancelada.\n", worker_num);
else
printf(“Hebra %d terminada con error %#lx.\n",
worker_num, exit_value);
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
76
V1.3
38
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/*
Tomamos la lista de primos encontrados
ordenamos de menor a mayor. Puesto que
hay ninguna garantía respecto al orden
Por tanto, es necesaria la ordenación.
por las hebras trabajadoras y los
las hebras han trabajado en paralelo no
en que están almacenados los primos.
Algoritmo de la burbuja: ¡lo siento!
*/
for (index1 = 1; index1 < request; index1++) {
for (index2 = 0; index2 < index1; index2++) {
if (primes[index1] < primes[index2]) {
temp = primes[index2];
primes[index2] = primes[index1];
primes[index1] = temp;
}
}
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
77
V1.3
Arquitectura e Ingeniería de Computadores
Programa ejemplo
/*
Imprimir la lista de primos obtenidos.
*/
printf(“La lista de %d primos es la siguiente:\n2", request);
for (list = 1, line_idx = 1; list < request; list++, line_idx++) {
if (line_idx >= 4) {
printf (",\n");
line_idx = 0;
}
else if (line_idx > 0) {
printf(",\t");
}
printf("%d", primes[list]);
}
printf("\n");
}
© J. A. de Frutos Redondo, R. Durán 2006
6. Programación paralela de sistemas SMP
78
V1.3
39
Descargar