Ressources informatiques

Ressources informatiques

Ressources informatiques

Utiliser Thread et MUTEX (API pthread)

Documentation : perkamon.traduc.org : pthread_create() pthread_join()

Lancement et terminaison Variable partagée et MUTEX

Lancement et terminaison

Syntaxe des fonctions utilisées

pthread_create()
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

Démarre un nouveau thread dans le processus appelant.

pthread_join()
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

Exemple

Programme qui lance un thread. Ce thread affiche un message qui lui a été passé en paramètre à son lancement.
Pour compiler, il est nécessaire d'utiliser : g++ -o thread1 -pthread thread1.c

/* compilation : g++ -o thread1 -pthread thread1.c */
#include <stdio.h>      // pour perror(), printf()
#include <pthread.h>    // pour les thread
#include <stdlib.h>     // pour malloc(), free()

static void *threadAfficher(void * argument);

int main() {
   pthread_t idThread; // Identifiant du thread

   char *message = (char *)"bonjour";
   int *retourThread;

   // Démarrer un thread
   if (pthread_create (&idThread, NULL, &threadAfficher, (void *)message) != 0)
   {perror("pthread_create() :"); return -1;}
   printf("Thread créé.\n");

   // Attendre la fin du thread
   if (pthread_join(idThread, (void **)&retourThread) != 0)
   {perror("pthread_join() :"); return -1;}

   printf("Thread terminé avec le code %d\n", *retourThread);

   // Libérer la mémoire alloué par le thread
   free(retourThread);

   return 0;
}

static void *threadAfficher(void *  argument) {
   int *retour;

   // Allouer la mémoire pour la variable retour 
   retour = (int *)malloc(sizeof(int));
   *retour = 0;

   // Afficher le message 
   char * message = (char *)argument;
   printf("Thread : %s\n", message);

   return retour;
}
Résultats d'exécutions
doe@debian:~/threads$ ./thread1
Thread créé.
Thread : bonjour
Thread terminé avec le code 0

J'ajoute une pause de 10 s avant l'intruction return retour; dans le thread. Cela me permet d'exécuter le programme thread1 en arrière plan à l'aide du & et de saisir la commande ps -Lf afin de visualiser les threads en cours d'exécution.
Le thread père et son identifiant sont surlignés en bleu, le thread fils et son identifiant sont surlignés en vert. Le thread père correspond à la fonction main().
Les deux threads ont le même pid car ils sont encapsulés dans le même processus.

doe@debian:~/threads$ ./thread1 &
[1] 7322
doe@debian:~/threads$ Thread créé.
Thread : bonjour
ps -Lf
UID        PID  PPID   LWP  C NLWP STIME TTY          TIME CMD
doe       2233  2190  2233  0    1 10:02 pts/0    00:00:00 bash
doe       7322  2233  7322  0    2 10:59 pts/0    00:00:00 ./thread1
doe       7322  2233  7323  0    2 10:59 pts/0    00:00:00 ./thread1
doe       7328  2233  7328  0    1 10:59 pts/0    00:00:00 ps -Lf
doe@debian:~/threads$ Thread terminé avec le code 0
[1]+  Fini                    ./thread1

Variable partagée et MUTEX

Caractéristiques d'un thread

Deux threads (activités) d'un même processus possède une zone de données communes (ZDC) : cela signifie qu'ils pourront partager des variables.
Cependant il NE DOIVENT PAS y accèder simultanément car cela risque de produire un résultat incohérent.
Il est donc nécessaire de rendre l'accès à la variable exclusif (c'est à dire que si un thread accède à une variable pour accéder à sa valeur ou la modifier, l'autre thread ne pourra y accéder). Cela est réalisé à l'aide d'un sémaphore d'exclusion mutuelle appelé MUTEX.
Il est important que le temps d'accès exclusif à la variable soit le plus court possible afin de ne pas nuire à la fluidité du programme.

Syntaxe des fonctions utilisées

pthread_mutex_lock(), pthread_mutex_unlock()
#include <pthread.h>
pthread_mutex_t fastmutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

Verrouille/déverrouille un sémaphore d'exclusion mutuelle (MUTEX).

Exemple

Programme qui lance deux thread : le premier incrémente une variable partagée toutes les secondes pendant dix secondes ; Le second affiche le contenu de la variable partagée toutes les secondes pendant dix secondes.

/* compilation : g++ -o thread2 -pthread thread2.c */
#include <stdio.h>      // pour perror(), printf()
#include <pthread.h>    // pour les thread
#include <stdlib.h>     // pour malloc(), free()
#include <unistd.h>     // pour sleep()

static void *threadAfficher(void * argument);
static void *threadModifier(void * argument);

int x = 0;// Variable partagee
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// MUTEX

int main() {
   pthread_t idThreadAfficher; // Identifiant du thread afficher
   pthread_t idThreadModifier; // Identifiant du thread modifier

   char *message1 = (char *)"Thread afficher";

   int *retourThreadAfficher;
   int *retourThreadModifier;

   // Créer le premier thread  
   if (pthread_create (&idThreadModifier, NULL, &threadModifier, NULL) != 0)
   {perror("pthread_create() modifier :"); return -1;}
   printf("Thread modifier créé.\n");

   // Créer le second thread  
   if (pthread_create (&idThreadAfficher, NULL, &threadAfficher, (void *)message1) != 0)
   {perror("pthread_create() afficher :"); return -1;}
   printf("Thread afficher créé.\n");

   // Attendre la fin du second thread  
   if (pthread_join(idThreadAfficher, (void **)&retourThreadAfficher) != 0)
   {perror("pthread_join() afficher :"); return -1;}

   printf("Thread afficher terminé avec le code %d\n", *retourThreadAfficher);
   free(retourThreadAfficher);

   // Attendre la fin du premier thread */
   if (pthread_join(idThreadModifier, (void **)&retourThreadModifier) != 0)
   {perror("pthread_join() modifier :"); return -1;}

   printf("Thread modifier terminé avec le code %d\n", *retourThreadModifier);
   free(retourThreadModifier);

   return 0;
}

static void *threadModifier(void * argument) {
   int *retour;
   int i;

   // Allouer de la mémoire pour la variable retour
   retour = (int *)malloc(sizeof(int));
   *retour = 0;

   // Toutes les secondes pendant 10 secondes
   for (i = 0; i < 10; i++) {
      // Vérouiller le MUTEX
      pthread_mutex_lock(&mutex);

      // Incrémenter la variable partagée
      x++;

      // Dévérouiller le MUTEX
      pthread_mutex_unlock(&mutex);

      sleep(1);
   }
   return retour;
}

static void *threadAfficher(void * argument) {
   int *retour;
   int i;
   int xLoc; // variable locale

   // Allouer de la mémoire pour la variable retour
   retour = (int *)malloc(sizeof(int));
   *retour = 0;

   char *message = (char *)argument;

   // Toutes les secondes pendant 10 secondes
   for (i = 0; i <10; i++) {
      // Vérouiller le MUTEX
      pthread_mutex_lock(&mutex);

      // Copier la variable partagée dans une variable locale
      xLoc = x;

      // Dévérouiller le MUTEX
      pthread_mutex_unlock(&mutex);

      // Afficher la variable locale
      printf("%s : x vaut %d\n", message, xLoc);
      fflush(stdout);

      sleep(1);
   }

   return retour;
}

L'affichage de la valeur de la variable partagée est effectuée après copie dans une variable locale afin de minimiser la durée pendant laquelle le MUTEX est vérouillé.

Résultat d'exécution
doe@debian:~/threads$ g++ -o thread2 -pthread thread2.c
doe@debian:~/threads$ ./thread2
Thread modifier créé.
Thread afficher créé.
Thread afficher : x vaut 0
Thread afficher : x vaut 1
Thread afficher : x vaut 2
Thread afficher : x vaut 3
Thread afficher : x vaut 4
Thread afficher : x vaut 5
Thread afficher : x vaut 6
Thread afficher : x vaut 7
Thread afficher : x vaut 8
Thread afficher : x vaut 9
Thread afficher terminé avec le code 0
Thread modifier terminé avec le code 0