Cours n°5 :
Processus légers
(threads)

Réseau et Prog. Bas Niveau
Victor Poupet

Processus légers

Si l'on veut exécuter plusieurs tâches en parallèle, on peut utiliser des processus différents

Pour exécuter plusieurs instances de la même tâche en parallèle, on peut utiliser des threads (processus légers) à la place des processus

Processus légers

threadsthreads

En C

#include <pthread.h>

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)

void pthread_exit(void *value_ptr)

La bibliothèque pthread permet de créer et gérer des threads dans un processus

Exemple

#include <stdio.h>

#define NB_CASES 1000

void tache(int *tab) {
  int i;
  for (i = 0; i < NB_CASES; i++) {
    tab[i]=i*i;
  }
}#include <stdio.h>

#define NB_CASES 1000
#define NB_THREADS 4

void tache(int deb, int fin, int *tab) {
  int i;
  for (i = deb; i < fin; i++) {
    tab[i]=i*i;
  }
}#include <pthread.h>
#include <stdio.h>

#define NB_CASES 1000
#define NB_THREADS 4

struct ThreadArgs {
  int debut;
  int fin;
  int *tab;
};

void* tache(void* args) {
  struct ThreadArgs *a = args;
  int i;
  for (i = a->debut; i < a->fin; i++) {
    a->tab[i]=i*i;
  }
  return NULL;
}
int main(void) {
  int i, tab[NB_CASES];

  tache(tab);

  for (i = 0; i < NB_CASES; i++) {
    printf("%d ", tab[i]);
  }
  return 0;
}int main(void) {
  int i, tab[NB_CASES];

  for (i = 0; i < NB_THREADS; i++) {
    debut = i * NB_CASES / NB_THREADS;
    fin = (i+1) * NB_CASES / NB_THREADS;
    tache(debut, fin, tab);
  }

  for (i = 0; i < NB_CASES; i++) {
    printf("%d ", tab[i]);
  }
  return 0;
}int main(void) {
  int i, tab[NB_CASES];
  struct ThreadArgs args[NB_THREADS];
  pthread_t threads[NB_THREADS];

  for (i = 0; i < NB_THREADS; i++) {
    args[i].debut = i * NB_CASES / NB_THREADS;
    args[i].fin = (i+1) * NB_CASES / NB_THREADS;
    args[i].tab = tab;
    pthread_create(&threads[i], NULL, tache, &args[i]);
  }
  for (i = 0; i < NB_THREADS; i++) {
    pthread_join(threads[i], NULL);
  }
  for (i = 0; i < NB_CASES; i++) {
    printf("%d ", tab[i]);
  }
  return 0;
}

Concurrence

#include <pthread.h>
#include <stdio.h>
#define NB_THREADS 4

void* incr(void *arg) {
  int i;
  int *p = arg;
  for (i = 0; i < 10000; i++) {
    (*p)++;
  }
  return NULL;
}
int main(void) {
  int i, c=0;
  pthread_t threads[NB_THREADS];
  for (i = 0; i < NB_THREADS; i++) {
    pthread_create(&threads[i], NULL, incr, &c); }
  for (i = 0; i < NB_THREADS; i++) {
    pthread_join(threads[i], NULL); }
  printf("%d\n", c);
  return 0;
}

Les threads partagent le même espace mémoire

Thread safety

#include <pthread.h>

int incr() {
  static int c = 0;
  static pthread_mutex_t mutex;
  mutex = PTHREAD_MUTEX_INITIALIZER;

  // bloquer le verrou
  pthread_mutex_lock(&mutex);

  c++;
  int r = c; // sauvegarder résultat

  // libérer le verrou
  pthread_mutex_unlock(&mutex);

  return r;
}

Réentrance

// cette fonction est réentrante
void echange(int *x, int *y) {
  int t;
  t = *x;
  *x = *y;
  *y = t;
}int t;

// cette fonction n'est pas réentrante
void echange(int *x, int *y) {
  t = *x;
  *x = *y;
  *y = t;
}int t;

// cette fonction est réentrante
void echange(int *x, int *y) {
  int s = t;
  t = *x;
  *x = *y;
  *y = t;
  t = s;
}

Une fonction est dite réentrante si elle se comporte correctement lorsqu'elle est appelée pendant une exécution d'elle-même

Compilation

$ gcc prog.c -lpthread -D_REENTRANT$ gcc prog.c -pthread

ProcessusThread