Utiliser Thread et MUTEX (Librairie (C11))
Documentation : en.cppreference.com > Concurrency support library
| Lancement et terminaison | Variable partagée et MUTEX | Cas particulier des types de bases > atomic |
Lancement et terminaison
Syntaxe des fonctions utilisées
thrd_create
#include <threads.h> int thrd_create( thrd_t *thr, thrd_start_t func, void *arg );
Démarre un nouveau thread dans le processus appelant.
- *thr : identifiant du thread démarré.
- func : fonction contenant le code exécuté par le thread
- *arg : unique argument passé en paramètre de la fonction.
- Valeur de retour :
thrd_succesen cas de succès
thrd_join
Attend que le thread identifié par le paramètre thr se termine.
#include <threads.h> int thrd_join( thrd_t thr, int *res );
- thr : identifiant du thread attendu.
- *res : emplacement où stocker la valeur retournée par le thread.
- Valeur de retour :
thrd_succes
Exemple : thread1.c
/*
* Description : Programme qui lance un thread. Ce thread affiche un message qui lui a été passé
* en paramètre à son lancement.
* Compilation : gcc -o thread1 -std=c11 -pthread thread1.c
*/
#include <stdio.h> // pour perror(), printf()
#include <threads.h> // pour les thread
static int threadAfficher(void * argument);
int main() {
thrd_t idThread; // Identifiant du thread
char *message = (char *)"bonjour";
int retourThread;
// Démarrer un thread
if (thrd_create (&idThread, &threadAfficher, (void *)message) != thrd_success)
{perror("thrd_create() :"); return -1;}
printf("Thread créé.\n");
// Attendre la fin du thread
if (thrd_join(idThread, &retourThread) != thrd_success)
{perror("thrd_join() :"); return -1;}
printf("Thread terminé avec le code %d\n", retourThread);
return 0;
}
static int threadAfficher(void * argument) {
// Afficher le message
const char * message = (const char *)argument;
printf("Thread : %s\n", message);
return thrd_success;
}
Résultats d'exécutions
doe@serveur:~$ ./thread1 Thread créé. Thread : bonjour Thread terminé avec le code 0
J'ajoute une pause de 10 s avant l'intruction return thrd_success; 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@serveur:~$ ./thread1 & ps -LF [1] 1039 Thread créé. Thread : bonjour UID PID PPID LWP C NLWP SZ RSS PSR STIME TTY TIME CMD doe 970 969 970 0 1 2061 4988 0 16:18 pts/0 00:00:00 -bash doe 1039 970 1039 0 2 2678 536 1 16:24 pts/0 00:00:00 ./thread1 doe 1039 970 1041 0 2 2678 536 1 16:24 pts/0 00:00:00 ./thread1 doe 1040 970 1040 0 1 2463 3448 0 16:24 pts/0 00:00:00 ps -LF
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
#include <threads.h> int mtx_init( mtx_t * mutex, int type ); // initalise le sémaphore int mtx_lock( mtx_t * mutex ) // verrouille le sémaphore int mtx_unlock( mtx_t * mutex ); // déverrouille le sémaphore
- mutex : adresse du MUTEX
- type : mtx_plain : mutex bloquant, non limité dans le temps.
- Valeur de retour :
thrd_succesen cas de succès
Exemple
/*
* description : 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 : gcc -o thread2 -std=c11 -pthread thread2.c
*/
#include <stdio.h> // pour perror(), printf()
#include <threads.h> // pour les thread
#include <unistd.h> // pour sleep()
static int threadAfficher(void *argument);
static int threadModifier(void *argument);
int x = 0; // Variable partagee
mtx_t mutex;
int main()
{
thrd_t idThreadAfficher; // Identifiant du thread afficher
thrd_t idThreadModifier; // Identifiant du thread modifier
char *message1 = (char *)"Thread afficher";
char *message2 = (char *)"Thread modifier";
int retourThread;
// Initialiser le mutex
mtx_init(&mutex, mtx_plain);
// Créer le premier thread
if (thrd_create(&idThreadModifier, &threadModifier, (void *)message2) != thrd_success)
{
perror("thrd_create() :");
return -1;
}
printf("Thread modifier créé.\n");
// Créer le second thread
if (thrd_create(&idThreadAfficher, &threadAfficher, (void *)message1) != thrd_success)
{
perror("thrd_create() :");
return -1;
}
printf("Thread afficher créé.\n");
// Attendre la fin du second thread
if (thrd_join(idThreadAfficher, &retourThread) != thrd_success)
{
perror("pthread_join() afficher :");
return -1;
}
printf("Thread afficher terminé avec le code %d\n", retourThread);
// Attendre la fin du thread
if (thrd_join(idThreadModifier, &retourThread) != thrd_success)
{
perror("thrd_join() :");
return -1;
}
printf("Thread modifier terminé avec le code %d\n", retourThread);
return 0;
}
static int threadModifier(void *argument)
{
// Afficher le message
const char *message = (const char *)argument;
printf("Thread : %s démarré\n", message);
for (int i = 0; i < 10; i++)
{
// Vérouiller le MUTEX
mtx_lock(&mutex);
// Incrémenter la variable partagée
++x;
// Déverrouiller le MUTEX
mtx_unlock(&mutex);
sleep(1);
}
return thrd_success;
}
static int threadAfficher(void *argument)
{
const char *message = (const char *)argument;
int xLoc; // variable locale
printf("Thread : %s démarré\n", message);
// Toutes les secondes pendant 10 secondes
for (int i = 0; i <10; i++)
{
// Verrouiller le MUTEX
mtx_lock(&mutex);
// Copier la variable partagée dans une variable locale
xLoc = x;
// Déverrouiller le MUTEX
mtx_unlock(&mutex);
// Afficher la variable locale
printf("%s : x vaut %d\n", message, xLoc);
fflush(stdout);
sleep(1);
}
return thrd_success;
}
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@serveur:~$ ./thread2 Thread modifier créé. Thread afficher créé. Thread : Thread afficher démarré Thread afficher : x vaut 0 Thread : Thread modifier démarré Thread afficher : x vaut 2 Thread afficher : x vaut 2 Thread afficher : x vaut 3 Thread afficher : x vaut 5 Thread afficher : x vaut 5 Thread afficher : x vaut 6 Thread afficher : x vaut 7 Thread afficher : x vaut 9 Thread afficher : x vaut 9 Thread afficher terminé avec le code 0 Thread modifier terminé avec le code 0
Variable partagée et type atomic
Les variables partagées correspondant à des types de bases peuvent être déclarée comme atomic ce qui permet de garantir l'unicité de la modification de la variable si et seulement si elles effectuent uniquement des opérations atomiques
/*
* description : Programme qui lance deux thread :
* qui incrémentent différentes variables.
* compilation : gcc -o thread3 -std=c11 -pthread thread3.c
*/
#include <stdio.h> // pour perror(), printf()
#include <threads.h> // pour les thread
#include <unistd.h> // pour sleep()
#include <stdatomic.h>
static int threadModifier(void *argument);
int x = 0; // Variable partagee
int x2 = 0; // Variable partagee
atomic_int xAtomic= 0; // Variable partagee atomic
atomic_int xAtomic2= 0; // Variable partagee atomic
int xNonProtege = 0; // Ne pas faire ca : pour les tests
mtx_t mutex; // sémaphore pour la variable x
mtx_t mutex2; // sémaphore pour la variable x2
int main()
{
thrd_t idThreadModifier1; // Identifiant du thread modifier
thrd_t idThreadModifier2; // Identifiant du thread modifier
char *message1 = (char *)"Thread modifier 1";
char *message2 = (char *)"Thread modifier 2";
int retourThread;
// Initialiser le mutex
mtx_init(&mutex, mtx_plain);
// Créer les thread
if (thrd_create(&idThreadModifier1, &threadModifier, (void *)message1) != thrd_success)
{
perror("thrd_create() :");
return -1;
}
printf("Thread modifier 1 créé.\n");
if (thrd_create(&idThreadModifier2, &threadModifier, (void *)message2) != thrd_success)
{
perror("thrd_create() :");
return -1;
}
printf("Thread modifier 2 créé.\n");
// Attendre la fin du thread
if (thrd_join(idThreadModifier2, &retourThread) != thrd_success)
{
perror("thrd_join() :");
return -1;
}
printf("Thread modifier 2 terminé avec le code %d\n", retourThread);
// Attendre la fin du thread
if (thrd_join(idThreadModifier1, &retourThread) != thrd_success)
{
perror("thrd_join() :");
return -1;
}
printf("Thread modifier 1 terminé avec le code %d\n", retourThread);
printf("\n---------------------------------------\n");
printf("Variable partagée protégée x : \t\t\t\t%d > 2000000 attendu\n", x);
printf("Variable partagée non protégée xNonProtege : \t\t%d > 2000000 attendu\n", xNonProtege);
printf("Variable atomic opéraion atomique xAtomic : \t\t%d > 2000000 attendu\n", xAtomic);
printf("---------------------------------------\n");
printf("Variable partagée protégée x2 : \t\t\t%d > 4000000 attendu\n", x2);
printf("Variable atomic opéraion non atomique xAtomic2 : \t%d > 4000000 attendu\n", xAtomic2);
return 0;
}
static int threadModifier(void *argument)
{
// Afficher le message
const char *message = (const char *)argument;
printf("Thread : %s démarré\n", message);
for (int i = 0; i < 1000000; i++)
{
mtx_lock(&mutex);
++x; // variable protégée
mtx_unlock(&mutex);
++xNonProtege; // variable non protégée : Ne pas faire ça
++xAtomic; // variable atomic effectuant une opération atomique -> OK
mtx_lock(&mutex2);
x2 = x2 + 2; // variable protégée
mtx_unlock(&mutex2);
xAtomic2 = xAtomic2 +2; // variable atomic effectuant une opération non atomique -> NOK : Ne pas faire ça
}
return thrd_success;
}
doe@serveur:~$ ./thread3 Thread modifier 1 créé. Thread modifier 2 créé. Thread : Thread modifier 2 démarré Thread : Thread modifier 1 démarré Thread modifier 2 terminé avec le code 0 Thread modifier 1 terminé avec le code 0 --------------------------------------- Variable partagée protégée x : 2000000 > 2000000 attendu Variable partagée non protégée xNonProtege : 1987723 > 2000000 attendu Variable atomic opéraion atomique xAtomic : 2000000 > 2000000 attendu --------------------------------------- Variable partagée protégée x2 : 4000000 > 4000000 attendu Variable atomic opéraion non atomique xAtomic2 : 3898044 > 4000000 attendu