0% ont trouvé ce document utile (0 vote)
10 vues9 pages

Introduction aux Threads Posix en C

Le document décrit les threads POSIX sous Linux. Il explique ce qu'est un thread, comment en créer un et attendre sa terminaison, et comment gérer les données partagées entre threads à l'aide de mutex pour éviter les problèmes de concurrence.

Transféré par

Agro Foresterie
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
10 vues9 pages

Introduction aux Threads Posix en C

Le document décrit les threads POSIX sous Linux. Il explique ce qu'est un thread, comment en créer un et attendre sa terminaison, et comment gérer les données partagées entre threads à l'aide de mutex pour éviter les problèmes de concurrence.

Transféré par

Agro Foresterie
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Chapitre 4 :

Les Threads Posix

1
1 Pointeurs de fonction
Un pointeur de fonctions en C est une variable qui permet de désigner une fonction C. Comme
n’importe quelle variable, on peut mettre un pointeur de fonctions soit en variable dans une
fonction, soit en paramètre dans une fonction.
On déclare un pointeur de fonction comme un prototype de fonction, mais on ajoute une
étoile (*) devant le nom de la fonction. Dans l’exemple suivant, on déclare dans le main un
pointeur sur des fonctions qui prennent en paramètre un int, et un pointeur sur des fonctions
qui retournent un int.

#include <stdio.h>
int SaisisEntier (void)
{
int n;
printf ("Veuillez entrer un entier : ");
scanf("%d", &n);
return n;
}
void AfficheEntier(int n)
{
printf ("L'entier n vaut %d\n", n);
}
int main(void)
{
void (*foncAff) (int); /* dclaration d'un pointeur foncAf f */
int (*foncSais) (void); /*dclaration d'un pointeur foncSais */
int entier;
foncSais = SaisisEntier; /* affectation d'une fonction */
foncAff = AfficheEntier; /* affectation d'une fonction */
entier = foncSais(); /* on excute la fonction */
foncAff (entier); /* on excute la fonction */
return 0;
}

Dans l’exemple suivant, la fonction est passée en paramètre à une autre fonction, puis
exécutée.
#include <stdio.h>
int SaisisEntier (void)
{
int n;
printf ("Veuillez entrer un entier : ");
scanf("%d", &n);
getchar();
return n;
}
void AfficheDecimal (int n)
{
printf ("L'entier n vaut %d\n", n);
}
void AfficheHexa(int n)
{
printf ("L'entier n vaut %x\n", n);
ESGIS-Master1-Les threads@2013-2014 2
}
void ExecAffiche(void (*foncAff) (int), int n)
{
foncAff(n); /* excution du paramtre */
}
int main(void)
{
int (*foncSais) (void); /*dclaration d'un pointeur foncSais */
int entier;
char rep;
foncSais = SaisisEntier; /* affectation d'une fonction */
entier = foncSais(); /* on excute la fonction */
puts("Voulez-vous afficher l'entier n en dcimal (d) ou en hexa (x) ?");
rep = getchar();
/* passage de la fonction en paramtre : */
if (rep == 'd')
ExecAffiche (AfficheDecimal, entier);
if (rep == 'x')
ExecAffiche (AfficheHexa, entier);
return 0;
}

2 Thread Posix (sous linux)


2.1 Qu’est-ce qu’un thread?
Un thread (ou fil d’exécution en français) est une partie du code d’un programme (une
fonction), qui se déroule parallèlement à d’autre parties du programme.
Un premier intérêt peut être d’effectuer un calcul qui dure un peu de temps (plusieurs
secondes, minutes, ou heures) sans que l’interface soit bloquée (le programme continue à
répondre aux signaux).
Les fonctions liées aux threads sont dans la bibliothèque pthread.h, et il faut compiler avec
la librairie libpthread.a :

$ gcc -lpthread monprog.c -o monprog

2.2 Création d’un thread et attente de terminaison


Pour créer un thread, il faut créer une fonction qui va s’exécuter dans le thread, qui a pour
prototype:
void *ma_fonction_thread (void *arg);
Dans cette fonction, on met le code qui doit être exécuté dans le thread. On crée
ensuite le thread par un appel à la fonction pthread_create, et on lui passe en argument la
fonction ma_fonction_thread dans un pointeur de fonction (et son argument arg). La fonction
pthread_create a pour prototype:
int pthread_create(pthread_t *thread, pthread_attr_t *attributs,
void * (*fonction) (void *arg), void *arg);
 Le premier argument est un passage par adresse de l’identifiant du thread (de type

3
pthread_t). La fonction pthread_create nous retourne ainsi l’identifiant du thread, que l’on
utilise ensuite pour désigner le thread.
 Le deuxième argument attributs désigne les attributs du thread, et on peut
mettre NULL pour avoir les attributs par défaut.
 Le troisième argument est un pointeur sur la fonction à exécuter dans le thread (par
exemple ma_fonction_thread, et
 le quatrième argument est l’argument de la fonction de thread.
Le processus qui exécute le main (l’équivalent du processus père) est aussi un thread et
s’appelle le thread principal. Le thread principal peut attendre la fin de l’exécution d’un autre
thread par la fonction pthread_join (similaire à la fonction wait dans le fork). Cette fonction
permet aussi de récupérer la valeur retournée par la fonction ma_fonction_thread du thread.
Le prototype de la fonction pthread_join est le suivant :
int pthread_join (pthread_t thread, void **retour);
Le pre mier para mè tre est l’ i dentifia nt du threa d ( que l’ on obtie nt dans
pthread_create), et le second paramètre est un passage par adresse d'un pointeur
qui permet de récupérer la valeur retournée par ma_fonction_thread.

5.2.3 Exemples
Le premier exemple crée un thread qui dort un nombre de secondes passé en argument,
pendant que le thread principal attend qu’il se termine.

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

void * process(void * arg)


// Un seul argument de type void *
{
int i;
fprintf(stderr, "Starting process %s\n", (char *) arg);
for (i = 0; i < 10000; i++)
{
// Ecrit sur la sortie standard
write(1, (char *) arg, 1);
}
return NULL;
}
int main (void)
{
int retcode;
pthread_t th_a, th_b;
void * retval;
// création de la thread th_a
retcode = pthread_create(&th_a, NULL, process, (void *) "a");
if (retcode != 0) fprintf(stderr, "create th_a failed %d\n", retcode), exit(1);
// création de la thread th_b
retcode = pthread_create(&th_b, NULL, process, (void *) "b");
if (retcode != 0) fprintf(stderr, "create th_b failed %d\n", retcode), exit(1);
// attente de la terminaison de la thread th_a
retcode = pthread_join(th_a, &retval);
if (retcode != 0) fprintf(stderr, "join a failed %d\n", retcode), exit(1);
// attente de la terminaison de la thread th_b

4
retcode = pthread_join(th_b, &retval);
if (retcode != 0) fprintf(stderr, "join b failed %d\n", retcode), exit(1);
return 0;
}

Le deuxième exemple crée un thread qui lit une valeur entière et la retourne au main.

#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include <time.h>

void *ma_fonction_thread(void *arg)


{int resultat;
printf("Je suis un thread. Veuillez entrer un entier\n");
scanf ("%d", &resultat);
pthread_exit((void*)resultat); /* termine le thread proprement */
}

int
main (void) {
int ret;
pthread_t my_thread;
/* on cre le thread */
ret = pthread_create (&my_thread, NULL, ma_fonction_thread, (void*) NULL);
if (ret !=0)
{
fprintf(stderr, "Erreur de créion du thread");
exit (1);
}
pthread_join (my_thread, (void*) &ret); /* on attend la fin du thread */
printf("Dans le main, ret = %d\n", ret);
return 0;
}

3 Donnée partagées et exclusion mutuelle


Lorsqu’un nouveau processus est créé par un fork, toutes les données (variables globales,
variables locales, mémoire allouée dynamiquement), sont dupliquées et copiées, et le
processus père et le processus fils travaillent ensuite sur des variables différentes.

Dans le cas de threads, la mémoire est partagée, c’est à dire que les variables globales sont
partagées entre les différents threads qui s’exécutent en parallèle. Cela pose des problèmes
lorsque deux threads différents essaient d’écrire et de lire une même donnée.
Deux types de problèmes peuvent se poser :

 Deux threads concurrents essaient en même temps de modifier une variable globale;
 Un thread modifie une structure de donnée tandis qu’un autre thread essaie de la
lire. Il est alors possible que le thread lecteur lise la structure alors que le thread
écrivain a écrit la donnée à moitié. La donnée est alors incohérente.

5
Pour accéder à des données globales, il faut donc avoir recours à un mécanisme d’exclusion
mutuelle, qui fait que les threads ne peuvent pas accéder en même temps à une donnée. Pour
celà, on introduit des données appelés mutex, de type pthread_mutex_t.
Un thread peut verrouiller un mutex, avec la fonction pthread_mutex_lock(), pour pouvoir
accéder à une donnée globale ou à un flot (par exemple pour écrire sur la sortie stdout). Une
fois l’accès terminé, le thread dévérouille le mutex, avec la fonction pthread_mutex_unlock().
Si un thread A essaie de verrouiller le un mutex alors qu’il est déjà verrouillé par un
autre thread B, le thread A reste bloqué sur l’appel de pthread_mutex_lock() jusqu’à ce
que le thread B dévérouille le mutex. Une fois le mutex dévérouillé par B, le thread A verrouille
immédiatement le mutex et son exécution se poursuit. Cela permet au thread B d’accéder
tranquillement à des variables globales pendant que le thread A attend pour accéder aux
mêmes variables.
Pour déclarer et initialiser un mutex, on le déclare en variable globale (pour qu’il soit
accessible à tous les threads) :

pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;

La fonction pthread_mutex_lock() , qui permet de verrouiller un mutex, a pour prototype:

int pthread _mutex _lock (pthread_mutex_t *mutex);

Il faut éviter de verrouiller deux fois un même mutex dans le même thread sans
le déverrouiller entre temps. Il y a un risque de blocage définitif du thread.
Certaines versions du système gèrent ce problème mais leur comportement n’est
pas portable.

La fonction pthread_mutex_unlock () , qui permet de déverrouiller un mutex, a pour prototype:


int pthread_mutex_unlock (pthread_mutex_t *mutex);

Dans l’exemple suivant, différents threads font un travail d’une durée aléatoire. Ce
travail est fait alors qu’un mutex est verrouillé.
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
pthread_mutex_t my_mutex = PTHREAD_MUTEX_INITIALIZER;
void* ma_fonction_thread (void *arg);

int main(void)
{
int i;
pthread_t thread[10];
srand(time(NULL));
for (i=0 ; i<10 ; i++)
pthread_create (&thread[i], NULL, ma_fonction_thread, (void*) i);
for (i=0 ; i<10 ; i++) pthread_join (thread [i], NULL);
return 0;
}
void* ma_fonction_thread (void*arg)
{
int num_thread = (int)arg;

6
int nombre_iterations, i, j, k,n;
nombre_iterations = rand()%8;
for (i=0 ; i<nombre_iterations ; i++)
{
n = rand()%10000;
pthread_mutex_lock (&my_mutex);
printf ("Le thread numero %d commence son calcul\n",
num_thread);
for (j=0 ; j<n ; j++)
for (k=0 ; k<n ; k++)
{}
printf ("Le thread numero %d a fini son calcul\n",
num_thread);
pthread_mutex_unlock (&my_mutex);
}
pthread_exit (NULL);
}
Voici un extrait de la sortie du programme. On voit qu’un thread peut travailler
tranquillement sans que les autres n’écrivent.

Le thread numero 9 a fini son calcul
Le thread numero 9 commence son calcul
Le thread numero 9 a fini son calcul
Le thread numero 9 commence son calcul
Le thread numero 9 a fini son calcul
Le thread numero 0 commence son calcul
Le thread numero 0 a fini son calcul
Le thread numero 0 commence son calcul
Le thread numero 0 a fini son calcul
Le thread numero 1 commence son calcul
Le thread numero 1 a fini son calcul
Le thread numero 2 commence son calcul
Le thread numero 2 a fini son calcul
Le thread numero 2 commence son calcu
….

5.4 Sémaphores
En général, une section critique est une partie du code où un processus ou un thread ne
peut rentrer qu’à une certaine condition. Lorsque le processus (ou un thread) entre dans la
section critique, il modifie la condition pour les autres processus/threads.
Par exemple, si une section du code ne doit pas être exécutée simultanément par
plus de n threads. Avant de rentrer dans la section critique, un thread doit vérifier
qu’au plus n-1 threads y sont déjà. Lorsqu’un thread entre dans la section critique, il
modifie la condition sur le nombre de threads qui se trouvent dans la section critique.
Ainsi, un autre thread peut se trouver empêché d’entrer dans la section critique.
La difficulté est qu’on ne peut pas utiliser une simple variable comme compteur. En
effet, si le test sur le nombre de thread et la modification du nombre de threads lors de
l’entrée dans la section critique se font séquentiellement par deux instructions, si l’on joue
de malchance un autre thread pourrait tester la condition sur le nombre de threads
justement entre l’exécution de ces deux instructions, et deux threads passeraient en
même temps dans la section critique. Il y a donc nécessité de tester et modifier la
condition de manière atomique, c’est à dire qu’aucun autre processus/thread ne peu

7
rien exécuter entre le test et la modification. C’est une opération atomique appelée
Test and Set Lock.
Les sémaphores sont un type sem_t et un ensemble de primitives de base qui
permettent d’implémenter des conditions assez générales sur les sections critiques. Un
sémaphore possède un compteur dont la valeur est un entier positif ou nul. On
entre dans une section critique si la valeur du compteur est strictement positive.
Pour utiliser une sémaphore, on doit le déclarer et l’initialiser à une certaine valeur avec
la fonction sem_init

int sem_init(sem_t *semaphore, int pshared, unsigned int


value);

Le premier argument est un passage par adresse du sémaphore, le


deuxième argument indique si le sémaphore peut être partagé par plusieurs
processus, ou seulement par les threads du processus appelant (partage égale 0).
Enfin, le troisième argument est la valeur initiale du sémaphore.

Après utilisation, il faut systématiquement libérer le sémaphore avec la fonction


sem_destroy.
int sem_destroy(sem_t *sem);

Les primitives de bases sur les sémaphores sont :


 sem_wait : Reste bloquée si le sémaphore est nul et sinon décrémente le
compteur (opération atomique);
 sem_post : incrémente le compteur;
 sem_getvalue : récupère la valeur du compteur dans une variable passée par
adresse;
 sem_trywait : teste si le sémaphore est non nul et décrémente le
sémaphore, mais sans bloquer. Provoque une erreur en cas de valeur nulle
du sémaphore.

Les prototypes des fonctions sont

int sem_wait (sem_t * semaphore)


int sem_post(sem_t *semaphore)
int sem_getvalue(sem_t *semaphore, int *valeur)

Exemple. Le programme suivant permet au plus n sémaphores dans la section critique,


où n est passé en argument.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
sem_t semaphore;/* variable globale : s maphore */
void* ma_fonction_thread(void *arg);
int main(int argc, char **argv)
{
int i;
pthread_t thread[10];
srand(time(NULL));
8
if (argc != 2)
{
printf("Usage : %s nbthreadmax\n", argv[0]);
exit(0);
}
sem_init(&semaphore, 0, atoi(argv[1])); /* initialisation */
/* cr ation des threads */
for (i=0 ; i<10 ; i++)
pthread_create(&thread[i], NULL, ma_fonction_thread, (void*)i);
/* attente */
for (i=0 ; i<10 ; i++)
pthread_join(thread[i], NULL);
sem_destroy(&semaphore);
return 0;
}
void* ma_fonction_thread(void *arg)
{
int num_thread = (int)arg; /* num ro du thread */
int nombre_iterations, i, j, k, n;
nombre_iterations = rand()%8+1;
for (i=0 ; i<nombre_iterations ; i++)
{
sem_wait(&semaphore);
printf("Le thread %d entre dans la section critique\n",num_thread);
sleep(rand()%9+1);
printf("Le thread %d sort de la section critique\n",num_thread);
sem_post(&semaphore);
sleep(rand()%9+1);
}
pthread_exit(NULL);
}

Exemples de trace :
Le thread 1 entre dans la section critique
Le thread 2 entre dans la section critique
Le thread 0 entre dans la section critique
Le thread 3 entre dans la section critique
Le thread 4 entre dans la section critique
Le thread 0 sort de la section critique
Le thread 5 entre dans la section critique
Le thread 3 sort de la section critique
Le thread 9 entre dans la section critique
Le thread 9 sort de la section critique
Le thread 8 entre dans la section critique
Le thread 4 sort de la section critique
Le thread 7 entre dans la section critique
Le thread 1 sort de la section critique
Le thread 6 entre dans la section critique
Le thread 5 sort de la section critique

Vous aimerez peut-être aussi