Auxiliar 1

Introducción a Pthreads

Vicente González

Presentación

Auxiliar de la palabra auxilio

Su auxiliar

Su auxiliar

  • Vicente González
  • Auxiliar de
    • PSS
    • Computación en GPU
    • Sistemas Operativos
    • Metodologías de Diseño y Programación
  • Siempre disponible en persona 😃
  • Lento para responder correos 😟
  • Doble titulación
  • No duden en preguntar

Contexto

Paralelismo en C

Procesos Pesados

  • Fork
  • No comparten memoria
  • Costosos de instanciar
  • Seguros

Procesos livianos

  • Pthreads
  • Comparten memoria
  • Baratos de instanciar
  • Datarraces

Creación de threads

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr, 
                   void *(*start_routine)(void *), 
                   void *arg);

Compuesto por:

Creación de threads

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr, 
                   void *(*start_routine)(void *), 
                   void *arg);

Compuesto por:

  • La referencia al proceso

Creación de threads

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr, 
                   void *(*start_routine)(void *), 
                   void *arg);

Compuesto por:

  • La referencia al proceso
  • Atributos especiales del proceso (NULL)

Creación de threads

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr, 
                   void *(*start_routine)(void *), 
                   void *arg);

Compuesto por:

  • La referencia al proceso
  • Atributos especiales del proceso (NULL)
  • La rutina a ejecutar por el proceso

Creación de threads

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr, 
                   void *(*start_routine)(void *), 
                   void *arg);

Compuesto por:

  • La referencia al proceso
  • Atributos especiales del proceso (NULL)
  • La rutina a ejecutar por el proceso
  • Los argumentos a pasar a la rutina

Creación de threads

int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr, 
                   void *(*start_routine)(void *), 
                   void *arg);
  • Retorna 0 si la creación fue exitosa

  • El thread termina cuando start_routine termina

  • Todo thread creado debe ser enterrado con

    int pthread_join(pthread_t thread, void **return_value);
  • Los thread no enterrados se convierte en zombies y no devuelven los recursos

  • La función pthread_join espera a que el thread termine

Ejemplo

#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;
}

¿Cómo puedo usar más argumentos?

Usamos una estructura!

Ejemplo

#include <stdio.h>
#include <pthread.h>

typedef struct {
  char* name;
  int age;
} Args;

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;
}

Ejemplo

#include <stdio.h>
#include <pthread.h>

typedef struct {
  char* name;
  int age;
} Args;

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
  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;
}

Ejemplo

#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;
}

How to?

Diseño

  1. Encontrar las partes paralelizables
  2. Crear la estructura que permita ingresar los argumentos necesarios
  3. Programar la rutina
  • A veces la rutina sólo ajusta los argumentos para llamar a otra función
  • En la estructura de los argumentos podemos guardar cualquier cosa

Esto es no pretende ser una receta, sino que una guía general

How to?

Lógica

  1. Lanzar los threads con sus argumentos correspondientes
  2. Si aplica, realizar trabajo en el thread principal
  3. Esperar a que el trabajo paralelizado termine
  4. Enterrar los resultados y recolectar los resultados
  • Antes del join no existe garantía de que el trabajo se haya terminado
  • Asegúrese de que exista paralelismo entre el create y el join

Esto es no pretende ser una receta, sino que una guía general

Problema

Sólo uno realmente

P1 — Buscar Factor

Paralelicemos esta función que busca cualquier factor de un número para acelerarla utilizando \(P\) cores

#include <pthread.h>

typedef unsigned long long ulonglong;
typedef unsigned int uint;

// busca un factor del número entero x en el rango [i, j]
uint buscarFactor(ulonglong x, uint i, uint j){
    for (uint k = i; k <= j; k++){
        if (x % k == 0)
            return k;
    }
    return 0;
}

Desafío: Lanzar \(P-1\) procesos y utilizar el principal en la búsqueda

P1 — Buscar Factor

Propuesto

¿Cómo harías para que todos los procesos terminen cuando se encuentre el primer factor?

#include <pthread.h>

typedef unsigned long long ulonglong;
typedef unsigned int uint;

// busca un factor del número entero x en el rango [i, j]
uint buscarFactor(ulonglong x, uint i, uint j){
    for (uint k = i; k <= j; k++){
        if (x % k == 0)
            return k;
    }
    return 0;
}

Hint: Quizás una variable global ayudaría

Fin

Ver otras auxiliares