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_succes
en 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_succes
en 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