Publicado el 22 de Marzo del 2020
1.211 visualizaciones desde el 22 de Marzo del 2020
285,2 KB
27 paginas
Creado hace 7a (29/01/2018)
Programación con Hilos
Contenido
• Conceptos generales de programación paralela
• Programación con hilos
• Pthreads
• Sincronización
• Exclusión mutua (Mutex)
• Variables condición (Var Condition)
Preliminares: Computación Paralela
• Paralelismo: Múltiples cálculos que se realizan
simultáneamente.
– A nivel de instrucción (pipelining)
– Paralelismo de datos (SIMD)
– Paralelismo de tareas (vergonzosamente paralelo)
• Concurrencia: Múltiples cálculos que pueden ser
realizados simultáneamente.
• Concurrencia vs. Paralelismo
Concurrencia y Paralelismo
do_another_thing()
do_one_thing()
do_wrap_up()
do_one_thing()
do_another_thing()
do_wrap_up()
do_one_thing()
do_wrap_up()
do_another_thing()
Tiempo
4
Procesos vs. Hilos
• Proceso (Process): Instancia de un programa que se
está ejecutando en su propio espacio de direcciones. En
sistemas POSIX, cada proceso mantiene su propio
heap, pila, registro, descriptores de ficheros, etc.
Comunicación:
• Memoria compartida
• Red
• Pipes, Colas
• Hilo (Thread): Una versión ligera del proceso que
comparte dirección con otros hilos. En sistemas POSIX,
cada hilo mantiene sus propios: registros, pila, señales.
Comunicación:
• Espacio de direcciones compartido
Concurrencia usando hilos
• Ejecución serializada:
– Hasta ahora… nuestros programas constan de un solo hilo.
– Un programa termina su ejecución cuando su hilo termina su
ejecución.
• Multi-hilos:
de ejecución
– Un programa es organizado como múltiples y concurrentes hilos
– El programa principal puede crear (spawns) muchos hilos
– El hilo puede comunicarse con otro(s) hilo(s).
– Ventajas potenciales:
• Mejora de rendimiento
• Mejores tiempos de respuesta
• Mejora en la utilización de recursos
• Menor cantidad de trabajo extra comparada con el uso de múltiples
procesos.
Programación Multihilos
Incluso en C, la programación multihilos puede ser
realizada de diversas formas:
– Pthreads: biblioteca POSIX C,
– OpenMP,
– Intel threading building blocks,
– Cilk (de CSAIL!),
– Grand central dispatch,
– CUDA (GPU) y
– OpenCL (GPU/CPU)
No todo el código es paralelizable
float params[10];
for (int i=0; i<10; i++)
do_something ( params[i] );
float params[10];
float prev = 0;
for (int i=0; i<10; i++)
prev = complicated ( params[i], prev);
Paralelizable
No paralelizable
No todo el código multi-hilos es seguro
int balance =500;
void deposit ( int sum ) {
int currbalance=balance ; /
...
currbalance+=sum ;
balance=currbalance ; /
∗
}
void withdraw ( int sum ) {
int currbalance=balance ; /
if ( currbalance >0)
currbalance−=sum ;
∗
balance=currbalance ; /
}
∗
... deposit (100); /
thread 1 /
... withdraw (50); /* thread 2 /∗
∗
thread 3 /
. .. withdraw(100); /
∗
∗
...
∗
read balance
∗
/
write balance
∗
/
∗
read balance
∗
/
write balance
∗
/
Escenario: T1(read),T2(read,write),T1(write) ,balance=600
Escenario: T2(read),T1(read,write),T2(write) ,balance=450
API del pthread
API:
• Gestión de Hilos: crear, unir, atributos
pthread__
• Exclusión mutua: crear, destruir exclusión mutua
pthread_mutex_
• Variables condición: crear, destruir, esperar y continuar
pthread_cond_
• Sincronización: lectura/escritura candados y barreras
pthread_rwlock_, pthread_barrier_
API:
#include <pthread.h>
gcc −Wall −O0 −o <output> file.c −pthread (no −l prefix)
Creación de hilos
int pthread_create( pthread_t *thread,
const pthread _attr _t *attr,
void ( start _routine )(
void *arg );
∗ ∗
void ), ∗
• Crea un nuevo hilo que tiene los atributos especificados por attr.
• Utiliza las opciones de defecto de atributos cuando attr es NULL.
• Si no se producen errores en la creación, se almacena el hilo en thread
• Se invoca la función start_routine(arg) en un hilo de ejecución
diferente.
• Regresa cero si triunfa la creación, diferente de cero si se produce un error.
void pthread_exit(void value_ptr);
• Se invoca implícitamente cuando termina la función thread.
• Análogo con exit()
∗
Ejemplo
#include <pthread . h>
#include <stdio.h>
#define NUM_THREADS 5
∗
void
∗
∗
threadid )
void PrintHello (
{
long tid ;
tid = ( long ) threadid ;
printf( " Hello World ! It's me, thread #%ld ! \ n " , tid ) ;
pthread _exit(NULL ) ;
}
int main ( int argc , char argv [ ] )
{
pthread_t threads [NUM_THREADS] ;
int r c ;
long t ;
for ( t =0; t <NUM_THREADS; t ++) {
printf ( " In main : c reat ing thread %l d \ n " , t ) ;
rc = pthread_create (& threads [ t ] , NULL, PrintHello( void
if ( rc ) {
printf("ERROR; return code from pthread_create ( ) i s %d \ n ", rc ) ;
exit( −1);
}
}
pthread_exit(NULL ) ;
}
) t ) ;
∗
Traza de salida
Sincronización: unión (joining)
•
"Joining" es una manera de lograr la sincronización entre hilos. Ejemplo:
int pthread_join(pthread_t thread, void
•
∗∗
value_ptr);
pthread_join() Bloquea el thread que hace la llamada hasta que el thread especificado como
atributo termina su ejecución.
Si value_ptr es distinto de null, contiene el estatus de finalización del thread invocado.
•
Otras formas de sincronización: exclusión mutua (mutex), variables de condición
Exclusión mutua
• Mutex (exclusión mutua) actúa como un “candado" para proteger el
• Solo un hilo a la vez “posee” el candado. Los hilos deben turnarse
acceso a un dato.
para acceder al dato.
int pthread_mutex_destroy ( pthread_mutex_t mutex ) ;
int pthread_mutex_init ( pthread_mutex_t *mutex ,
const pthread_mutexattr_t attr );
thread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
∗
– pthread_mutex_init() inicializa un “mutex”. Si los atributos valen
NULL, entonces se usan los valores de defecto de la librería.
– La macro PTHREAD_MUTEX_INITIALIZER puede usarse para
inicializar “mutexes” estáticos.
– pthread_mutex_destroy() destruye el “mutex”.
– Ambas funcionan regresan 0 si no hay error, y un valor distinto de cero
si se produce algún error.
Exclusión mutua
int pthread_mutex_lock ( pthread_mutex_t mutex ) ;
int pthread_mutex_trylock ( pthread_mutex_t mutex ) ;
int pthread_mutex_unlock ( pthread_mutex_t mutex ) ;
∗
∗
∗
• pthread_mutex_lock() bloquea (cierra) la variable mutex.
Si la variable mutex está bloqueada (cerrada), la función que
hizo la llamada se bloquea hasta que la variable mutex esté
disponible (no-bloqueada).
• pthread_mutex_trylock() es la versión no-bloqueante.
Si la variable mutex está bloqueada (cerrada), la llamada
regresa inmediatamente.
• pthread_mutex_unlock() desbloquea (abre) la variable
mutex.
Ejemplo revisado
∗
read balance
∗
/
int balance =500;
void deposit ( int sum ) {
int currbalance=balance ; /
...
currbalance+=sum ;
balance=currbalance ; /
∗
}
void withdraw ( int sum ) {
int currbalance=balance ; /
if ( currbalance >0)
currbalance−=sum ;
∗
balance=currbalance ; /
}
∗
... deposit (100); /
thread 1 /
... withdraw (50); /* thread 2 / ∗
∗
... withdraw(100); /
thread 3 /
∗
∗
...
write balance
∗
/
∗
read balance
∗
/
write balance
∗
/
Escenario: T1(read),T2(read,write),T1(write) ,balance=600
Escenario: T2(read),T1(read,write),T2(write) ,balance=450
int balance =500;
pthread_mutex_t mutexbalance=PTHREAD_MUTEX_INITIALIZER;
void deposit ( int sum ) {
pthread_mutex_lock(&mutexbalance );
{
∗
read balance
int currbalance=balance ; /
...
currbalance+=sum ;
balance=currbalance ; /
}
pthread_mutex_unlock(&mutexbalance );
write balance
∗
/
∗
∗
/
}
void withdraw ( int sum ) {
pthread_mutex_lock(&mutexbalance );
{
Usando
Exclusión Mutua
∗
read balance
int currbalance=balance ; /
if ( currbalance >0)
currbalance−=sum ;
balance=currbalance ; /
}
pthread_mutex_unlock(&mutexbalance );
write balance
∗
∗
/
∗
/
}
∗
∗
thread 1 /
... deposit (100); /
... withdraw (50); /* thread 2 / ∗
∗
∗
... withdraw(100); /
thread 3 /
...
Escenario: T1(read),T2(read,write),T1(write) ,balance=550
Escenario: T2(read),T1(read,write),T2(write) ,balance=550
Ejemplo: Versión secuencial del
producto de dos vectores
#include <stdio.h>
#include <stdlib.h>
typedef struct {
double *a;
double *b;
double sum;
int veclen; } DOTDATA;
#define VECLEN 100000
DOTDATA dotstr;
void dotprod() {
int start, end, i;
double mysum, *x, *y;
start=0;
end = dotstr.veclen;
x = dotstr.a;
y = dotstr.b;
mysum = 0;
for (i=start; i<end ; i++) {
mysum += (x[i] * y[i]);
}
dotstr.sum = mysum;
}
int main (int argc, char *argv[]) {
int i,len;
double *a, *b;
len = VECLEN;
a = (double*) malloc (len*sizeof(double));
b = (double*) malloc (len*sizeof(double));
for (i=0; i<len; i++) {
a[i]=1; b[i]=a[i]; }
dotstr.veclen = len;
dotstr.a = a;
dotstr.b = b;
dotstr.sum=0;
dotprod ();
printf ("Sum = %f \n", dotstr.sum);
free (a);
free (b);
}
Ejemplo: Versión concurrente del
producto de dos vectores
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct
{
double *a;
double *b;
double sum;
int veclen;
} DOTDATA;
#define NUMTHRDS 4
#define VECLEN 100000
DOTDATA dotstr;
pthread_t callThd[NUMTHRDS];
pthread_mutex_t mutexsum;
void *dotprod(void *arg)
{
int i, start, end, len ;
long offset;
double mysum, *x, *y;
offset = (long)arg;
len = dotstr.veclen;
start = offset*len;
end = start + len;
x = dotstr.a;
y = d
Comentarios de: Programación con Hilos (0)
No hay comentarios