Sincronización de Threads
Resumen de la clase pasada
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg);thread que ejecuta start_routine.start_routine recibe arg como argumento.thread se puede crear con atributos attr especiales (NULL).threadUn thread termina si:
Retorna start_routine.
Llamando a pthread_exit (no recomendado).
pthread_join espera a que el thread termine.Los thread no enterrados se convierte en zombies y no devuelven los recursos
#include <stdio.h>
#include <pthread.h>
void *thread(void *ptr) {
char* nombre = (char*) ptr; // Castear argumento
printf("Thread - %s\n", nombre); // Trabajo en paralelo
return NULL; // Retorno
}
int main() {
pthread_t pid_1, pid_2; // Guardar PID de los threads lanzados
char* nombre_1 = "primero";
char* nombre_2 = "segundo";
pthread_create(&pid_1, NULL, thread, nombre_1); // lanzar thread1
pthread_create(&pid_2, NULL, thread, nombre_2); // lanzar thread2
pthread_join(pid_1, NULL); // esperar thread 1
pthread_join(pid_2, NULL); // esperar thread 2
return 0;
}args)#include <stdio.h>
#include <pthread.h>
typedef struct {
char* name;
int age;
} Args;
void *thread(void *ptr) {
Args* a = (Args*) ptr; // Castear a la estructura
printf("Thread - %s (%d)\n", a->name, a->age); // Accedemos a los miembros con ->
return NULL; // Retorno
}
int main() {
pthread_t pid_1, pid_2; // Guardar PID de los threads lanzados
Args a1 = {"primero", 10} // inicializamos los args de t1
Args a2 = {"segundo", 20} // inicializamos los args de t2
pthread_create(&pid_1, NULL, thread, &a1); // la pasamos por referencia
pthread_create(&pid_2, NULL, thread, &a2); // la pasamos por referencia
pthread_join(pid_1, NULL); // esperar thread 1
pthread_join(pid_2, NULL); // esperar thread 2
return 0;
}threads con sus argumentos correspondientesthread principaljoin no existe garantía de que el trabajo se haya terminadocreate y el joinLa siguiente función es una implementación simple de quicksort:
#include <pthread.h>
void quicksort_seq(int a[], int i, int j) {
if (i < j) {
int h = particionar(a, i, j);
quicksort_seq(a, i, h - 1);
quicksort_seq(a. h + 1, j);
}
}Considere particionar como la función que selecciona el pivote y reordena el arreglo.
Los valores menores al pivote quedan a la izquierda y los mayores a la derecha.
Se le pide paralelizar la función tal que haga uso de \(N\) cores:
Invocaciones secuenciales independientes son directamente paralelizables
Lo nuevo
Cuando se trabaja en paralelo, nacen nuevos enemigos.
Al acceder a recursos compartidos desde varios procesos se pueden generar problemas como:
Dataraces
Variables se sobreescriben
Race conditions
Orden incorrecto de ejecución
Hambruna y Deadlocks
Un proceso no obtiene tiempo de ejecución
MUTual EXclusión
Garantiza la exclusión mutua, bloqueando el acceso a “zonas críticas”, las cuales son zonas del código donde se manipulan los recursos compartidos.
Hacen esperar a los procesos de manera eficiente hasta que se cumpla la condición para continuar la ejecución.
Para solicitar el mutex:
La función retorna solo para el primer proceso que pida el mutex, el resto queda esperando
Ningún proceso ha solicitado el mutex
Algún proceso ha solicitado el mutex y no ha sido liberado
Mala implementación 🤢
¿Dónde esta el error?
Hagamos un diagrama
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
int contador = 0;
void aumentar_cont() {
pthread_mutex_lock(&m);
contador++;
pthread_mutex_unlock(&m);
}Buena implementación 🤠
Repitamos el diagrama
Esto es mala idea porque mantiene ocupado al core
Es mejor “dormir” el proceso para desocupar el core
Para hacer esperar a un proceso:
Al entrar en espera, el proceso liberará el mutex
Al salir de espera, el proceso esperará el mutex y la función retornará cuando lo obtenga
wait se hace cargo de liberar y pedir el mutex asociado.broadcast despierta a todos los procesos en espera.signal despierta a un solo proceso sin orden garantizado.pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
while(contador < 10) {;}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}Mala implementación 🤢
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
while(contador < 10) {;}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}Busy waiting
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
while(contador < 10) {;}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}Hambruna
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
if(contador == 10){
pthread_cond_broadcast(&cond);
}
while(contador < 10){
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
if(contador == 10){
pthread_cond_broadcast(&cond);
}
while(contador < 10){
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}Condiciones añadidas
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
if(contador == 10){
pthread_cond_broadcast(&cond);
}
while(contador < 10){
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}Espera eficiente
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
if(contador == 10){
pthread_cond_broadcast(&cond);
}
while(contador < 10){
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}Zona crítica respetada
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int contador = 0;
int aumentar_contador_y_esperar_10(){
pthread_mutex_lock(&mutex);
contador++;
if(contador == 10){
pthread_cond_broadcast(&cond);
}
while(contador < 10){
pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
printf("Contador llegó a 10");
return 0;
}Buena implementación 🤠
Se necesita crear un sistemas para juntar exactamente una cantidad \(X\) de dinero:
Definir el tipo de datos Colecta.
Programar la función
Que crea y retorna una colecta para juntar meta pesos.
Programar la función
Que es invocada desde múltiples procesos para contribuir monto pesos. El valor de retorno de la función es el mínimo entre monto y lo que falta para llegar a la meta.
La función retornar una vez que la meta se cumpla
CC4302 — Sistemas Operativos