Différences entre versions de « C pthread »
(15 versions intermédiaires par 2 utilisateurs non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
= Introduction = | = Introduction = | ||
− | Les | + | Les threads ou ''processus légers'' sont utilisés pour paralléliser l'exécution dans un programme. Ils sont dits ''légers'' car ils s'exécutent dans le même contexte que le processus d'exécution principal qui les crée et consomment donc moins de mémoire / CPU. |
= Pré-requis = | = Pré-requis = | ||
Ligne 11 : | Ligne 11 : | ||
gcc -lpthread thread_exemple.c | gcc -lpthread thread_exemple.c | ||
</source> | </source> | ||
− | Pour inclure la librairie dans [[Eclipse_install| | + | Pour inclure la librairie dans [[Eclipse_install|Eclipse]] regardez [[C_devel#Ajouter_une_librairie_.C3.A0_un_projet|ici]]. |
+ | |||
= Premier thread = | = Premier thread = | ||
− | Dans ce premier exemple nous allons créer un thread qui affiche une message: | + | Dans ce premier exemple, nous allons créer un thread qui affiche une message: |
<source lang='c'> | <source lang='c'> | ||
#include <stdio.h> | #include <stdio.h> | ||
Ligne 22 : | Ligne 23 : | ||
printf("Nous sommes dans le thread.\n"); | printf("Nous sommes dans le thread.\n"); | ||
// Arrêt propre du thread | // Arrêt propre du thread | ||
− | pthread_exit( | + | pthread_exit(EXIT_SUCCESS); |
} | } | ||
Ligne 35 : | Ligne 36 : | ||
} | } | ||
</source> | </source> | ||
− | Dans | + | Dans cet exemple, nous utilisons la fonction suivante: |
<source lang='c'> | <source lang='c'> | ||
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); | int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); | ||
Ligne 42 : | Ligne 43 : | ||
* ''*attr'' correspond aux arguments de création du thread, passer à la fonction ''pthread_create''; | * ''*attr'' correspond aux arguments de création du thread, passer à la fonction ''pthread_create''; | ||
* ''*(*start_routine)'' est un pointeur vers la fonction exécutée par le thread; | * ''*(*start_routine)'' est un pointeur vers la fonction exécutée par le thread; | ||
− | * ''*arg'' est un pointeur vers les arguments passés en | + | * ''*arg'' est un pointeur vers les arguments passés en paramètres à la fonction ''start_routine''; |
− | Lorsque l'on exécute cet exemple on | + | * le code retour de la fonction varie entre : |
+ | ** ''0'' → lorsque tout s'est bien passé; | ||
+ | ** EAGAIN → lorsque les ressources sont insuffisantes ou le nombre de threads maximum est atteint; | ||
+ | ** EINVAL → lorsqu'un argument invalide est passé en paramètre; | ||
+ | ** EPERM → lorsqu'il y a un problème de droit sur l'ordonnanceur (passé en paramètre); | ||
+ | Lorsque l'on exécute cet exemple, on a le résultat suivant : | ||
<pre> | <pre> | ||
Avant la création du thread. | Avant la création du thread. | ||
Ligne 54 : | Ligne 60 : | ||
* ''*thread'' est une référence vers la variable qui va contenir le thread; | * ''*thread'' est une référence vers la variable qui va contenir le thread; | ||
* ''**retval'' est un pointeur vers un entier qui contiendra la valeur de retour du thread; | * ''**retval'' est un pointeur vers un entier qui contiendra la valeur de retour du thread; | ||
+ | * le code retour de la fonction varie entre : | ||
+ | ** ''0'' → lorsque tout s'est bien passé; | ||
+ | ** EDEADLK → lorsqu'il y a un ''deadlock''; | ||
+ | ** EINVAL → lorsque le thread n'est pas joignable; | ||
+ | ** ESRCH → si le thread n'existe pas; | ||
Ajoutez la ligne suivante : | Ajoutez la ligne suivante : | ||
<source lang='c'> | <source lang='c'> | ||
pthread_join(thread1, NULL); | pthread_join(thread1, NULL); | ||
</source> | </source> | ||
− | + | après l'appel de la fonction ''pthread_create'' pour avoir le résultat suivant: | |
<pre> | <pre> | ||
Avant la création du thread. | Avant la création du thread. | ||
Ligne 64 : | Ligne 75 : | ||
Après la création du thread. | Après la création du thread. | ||
</pre> | </pre> | ||
− | = Modification d'un variable = | + | |
+ | = Modification d'une variable = | ||
+ | Dans cet exemple nous allons incrémenter dans le thread un entier qui est déclaré dans le processus principal: | ||
+ | <source lang='c'> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <pthread.h> | ||
+ | |||
+ | void *thread_1(void *arg) { | ||
+ | int *i = (int *) arg; | ||
+ | (*i)++; | ||
+ | // Arrêt propre du thread | ||
+ | pthread_exit(NULL); | ||
+ | } | ||
+ | |||
+ | int main(void) { | ||
+ | // Création de la variable qui va contenir le thread | ||
+ | int i = 1; | ||
+ | pthread_t thread1; | ||
+ | printf("Avant la création du thread, i = %i.\n", i); | ||
+ | // Création du thread | ||
+ | pthread_create(&thread1, NULL, thread_1, &i); | ||
+ | pthread_join(thread1, NULL); | ||
+ | printf("Après la création du thread, i = %i.\n", i); | ||
+ | return EXIT_SUCCESS; | ||
+ | } | ||
+ | </source> | ||
+ | Cela donne le résultat suivant : | ||
+ | <pre> | ||
+ | Avant la création du thread, i = 1. | ||
+ | Après la création du thread, i = 2. | ||
+ | </pre> | ||
+ | On peut remarquer que: | ||
+ | * dans le processus principal, on passe une référence vers la variable ''i'' (''&i'') dans la fonction ''pthread_create''; | ||
+ | * dans le thread on récupère le pointeur vers cette variable et on la déclare comme étant un entier : | ||
+ | <source lang='c'> | ||
+ | int *i = (int *) arg; | ||
+ | </source> | ||
+ | * on incrémente la valeur et non l'adresse pointée : | ||
+ | <source lang='c'> | ||
+ | (*i)++; | ||
+ | </source> | ||
+ | |||
+ | = Exclusion mutuelle = | ||
+ | Lorsque l'on cherche à modifier dans plusieurs threads une variable globale, il peut y avoir un problème d'accès concurrent (modification / lecture simultanée). Ce problème peut être réglé en utilisant ce que l'on appelle un ''mutex'' (''mutual exclusion'') et pour des questions pratiques, on utilise généralement une structure pour associer la variable à son ''mutex'': | ||
+ | |||
+ | <source lang="c"> | ||
+ | typedef struct mutex_data { | ||
+ | int data; | ||
+ | pthread_mutex_t mutex; | ||
+ | } mutex_data; | ||
+ | </source> | ||
+ | |||
+ | Dans l'exemple ci-dessus on crée une structure de données avec un entier et un ''mutex''. | ||
+ | Il existe plusieurs fonctions pour manipuler les ''mutex'' et voici les principales: | ||
+ | *pour initialiser un ''mutex'': | ||
+ | <source lang="c"> | ||
+ | int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr ) | ||
+ | </source> | ||
+ | *pour verrouiller un ''mutex'': | ||
+ | <source lang="c"> | ||
+ | int pthread_mutex_lock(pthread_mutex_t *mutex) | ||
+ | </source> | ||
+ | *pour déverrouiller un ''mutex'': | ||
+ | <source lang="c"> | ||
+ | int pthread_mutex_unlock(pthread_mutex_t *mutex) | ||
+ | </source> | ||
+ | *pour détruire un ''mutex'': | ||
+ | <source lang="c"> | ||
+ | int pthread_mutex_destroy(pthread_mutex_t *mutex) | ||
+ | </source> | ||
+ | |||
+ | Le programme suivant permet d'incrémenter la valeur d'un entier dans plusieurs threads différents: | ||
+ | <source lang="c"> | ||
+ | #include <pthread.h> | ||
+ | #include <stdio.h> | ||
+ | #include <stdlib.h> | ||
+ | #include <string.h> | ||
+ | #include <unistd.h> | ||
+ | |||
+ | // Nombre total de thread | ||
+ | #define NB_THREAD 2 | ||
+ | // Limite de l'incrément | ||
+ | #define INCREMENT_LIMIT 10 | ||
+ | |||
+ | // Tableau contenant les threads | ||
+ | pthread_t threads[NB_THREAD]; | ||
+ | |||
+ | // Structure de données contenant le mutex | ||
+ | typedef struct mutex_data { | ||
+ | int data; | ||
+ | pthread_mutex_t mutex; | ||
+ | } mutex_data; | ||
+ | |||
+ | // Fonction exécutée dans le thread | ||
+ | void * job(void *arg) { | ||
+ | mutex_data *md = (mutex_data*) arg; | ||
+ | pthread_t tid = pthread_self(); | ||
+ | while ((*md).data < INCREMENT_LIMIT) { | ||
+ | // Vérouillage du mutex | ||
+ | pthread_mutex_lock(&(*md).mutex); | ||
+ | (*md).data++; | ||
+ | // Dévérouillage du mutex | ||
+ | pthread_mutex_unlock(&(*md).mutex); | ||
+ | printf("thread [ %ld ] data [ %i ]\n", tid, (*md).data); | ||
+ | // Pause l'exécution du thread pendant 1 seconde | ||
+ | sleep(1); | ||
+ | } | ||
+ | printf("Fin du thread %ld\n", tid); | ||
+ | pthread_exit(NULL); | ||
+ | } | ||
+ | |||
+ | // Fonction principale | ||
+ | int main() { | ||
+ | // Création du mutex | ||
+ | mutex_data md; | ||
+ | // Initialisation de la donnée | ||
+ | md.data = 0; | ||
+ | // Initialisation du mutex | ||
+ | if (pthread_mutex_init(&md.mutex, NULL) != 0) { | ||
+ | printf("\n mutex init failed\n"); | ||
+ | return EXIT_FAILURE; | ||
+ | } | ||
+ | // Boucle de création des threads | ||
+ | for (int i = 0; i < NB_THREAD; i++) { | ||
+ | // Création du thread et passage de la structure par référence | ||
+ | int err = pthread_create(&threads[i], NULL, job, &md); | ||
+ | if (err != 0) { | ||
+ | printf("Echec de la création du thread: [%s]", strerror(err)); | ||
+ | break; | ||
+ | } | ||
+ | printf("Création du thread numéro %ld\n", threads[i]); | ||
+ | } | ||
+ | // En attente des threads | ||
+ | for (int i = 0; i < NB_THREAD; i++) { | ||
+ | pthread_join(threads[i], NULL); | ||
+ | } | ||
+ | // Destruction du mutex | ||
+ | pthread_mutex_destroy(&md.mutex); | ||
+ | return EXIT_SUCCESS; | ||
+ | } | ||
+ | </source> | ||
+ | Un exemple de l'exécution de ce programme donne la sortie suivante : | ||
+ | <pre> | ||
+ | Création du thread numéro 140530420492032 | ||
+ | Création du thread numéro 140530410002176 | ||
+ | thread [ 140530410002176 ] data [ 1 ] | ||
+ | thread [ 140530420492032 ] data [ 2 ] | ||
+ | thread [ 140530410002176 ] data [ 3 ] | ||
+ | thread [ 140530420492032 ] data [ 4 ] | ||
+ | thread [ 140530410002176 ] data [ 5 ] | ||
+ | thread [ 140530420492032 ] data [ 6 ] | ||
+ | thread [ 140530420492032 ] data [ 7 ] | ||
+ | thread [ 140530410002176 ] data [ 8 ] | ||
+ | thread [ 140530420492032 ] data [ 9 ] | ||
+ | thread [ 140530410002176 ] data [ 10 ] | ||
+ | Fin du thread 140530420492032 | ||
+ | Fin du thread 140530410002176 | ||
+ | </pre> |
Version actuelle datée du 12 mars 2021 à 11:03
Introduction
Les threads ou processus légers sont utilisés pour paralléliser l'exécution dans un programme. Ils sont dits légers car ils s'exécutent dans le même contexte que le processus d'exécution principal qui les crée et consomment donc moins de mémoire / CPU.
Pré-requis
Pour pouvoir créer des threads il faut utiliser la librairie pthread (POSIX thread):
#include <pthread.h>
Ce qui donne en ligne de commande:
gcc -lpthread thread_exemple.c
Pour inclure la librairie dans Eclipse regardez ici.
Premier thread
Dans ce premier exemple, nous allons créer un thread qui affiche une message:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *thread_1(void *arg) {
printf("Nous sommes dans le thread.\n");
// Arrêt propre du thread
pthread_exit(EXIT_SUCCESS);
}
int main(void) {
// Création de la variable qui va contenir le thread
pthread_t thread1;
printf("Avant la création du thread.\n");
// Création du thread
pthread_create(&thread1, NULL, thread_1, NULL);
printf("Après la création du thread.\n");
return EXIT_SUCCESS;
}
Dans cet exemple, nous utilisons la fonction suivante:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
- *thread est une référence vers la variable qui va contenir le thread;
- *attr correspond aux arguments de création du thread, passer à la fonction pthread_create;
- *(*start_routine) est un pointeur vers la fonction exécutée par le thread;
- *arg est un pointeur vers les arguments passés en paramètres à la fonction start_routine;
- le code retour de la fonction varie entre :
- 0 → lorsque tout s'est bien passé;
- EAGAIN → lorsque les ressources sont insuffisantes ou le nombre de threads maximum est atteint;
- EINVAL → lorsqu'un argument invalide est passé en paramètre;
- EPERM → lorsqu'il y a un problème de droit sur l'ordonnanceur (passé en paramètre);
Lorsque l'on exécute cet exemple, on a le résultat suivant :
Avant la création du thread. Après la création du thread.
On ne voit pas le message du thread car le programme se termine sans attendre la fin de son exécution. Pour attendre la fin de l'exécution du thread, il faut utiliser la fonction suivante :
int pthread_join(pthread_t thread, void **retval);
- *thread est une référence vers la variable qui va contenir le thread;
- **retval est un pointeur vers un entier qui contiendra la valeur de retour du thread;
- le code retour de la fonction varie entre :
- 0 → lorsque tout s'est bien passé;
- EDEADLK → lorsqu'il y a un deadlock;
- EINVAL → lorsque le thread n'est pas joignable;
- ESRCH → si le thread n'existe pas;
Ajoutez la ligne suivante :
pthread_join(thread1, NULL);
après l'appel de la fonction pthread_create pour avoir le résultat suivant:
Avant la création du thread. Nous sommes dans le thread. Après la création du thread.
Modification d'une variable
Dans cet exemple nous allons incrémenter dans le thread un entier qui est déclaré dans le processus principal:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *thread_1(void *arg) {
int *i = (int *) arg;
(*i)++;
// Arrêt propre du thread
pthread_exit(NULL);
}
int main(void) {
// Création de la variable qui va contenir le thread
int i = 1;
pthread_t thread1;
printf("Avant la création du thread, i = %i.\n", i);
// Création du thread
pthread_create(&thread1, NULL, thread_1, &i);
pthread_join(thread1, NULL);
printf("Après la création du thread, i = %i.\n", i);
return EXIT_SUCCESS;
}
Cela donne le résultat suivant :
Avant la création du thread, i = 1. Après la création du thread, i = 2.
On peut remarquer que:
- dans le processus principal, on passe une référence vers la variable i (&i) dans la fonction pthread_create;
- dans le thread on récupère le pointeur vers cette variable et on la déclare comme étant un entier :
int *i = (int *) arg;
- on incrémente la valeur et non l'adresse pointée :
(*i)++;
Exclusion mutuelle
Lorsque l'on cherche à modifier dans plusieurs threads une variable globale, il peut y avoir un problème d'accès concurrent (modification / lecture simultanée). Ce problème peut être réglé en utilisant ce que l'on appelle un mutex (mutual exclusion) et pour des questions pratiques, on utilise généralement une structure pour associer la variable à son mutex:
typedef struct mutex_data {
int data;
pthread_mutex_t mutex;
} mutex_data;
Dans l'exemple ci-dessus on crée une structure de données avec un entier et un mutex. Il existe plusieurs fonctions pour manipuler les mutex et voici les principales:
- pour initialiser un mutex:
int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr )
- pour verrouiller un mutex:
int pthread_mutex_lock(pthread_mutex_t *mutex)
- pour déverrouiller un mutex:
int pthread_mutex_unlock(pthread_mutex_t *mutex)
- pour détruire un mutex:
int pthread_mutex_destroy(pthread_mutex_t *mutex)
Le programme suivant permet d'incrémenter la valeur d'un entier dans plusieurs threads différents:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
// Nombre total de thread
#define NB_THREAD 2
// Limite de l'incrément
#define INCREMENT_LIMIT 10
// Tableau contenant les threads
pthread_t threads[NB_THREAD];
// Structure de données contenant le mutex
typedef struct mutex_data {
int data;
pthread_mutex_t mutex;
} mutex_data;
// Fonction exécutée dans le thread
void * job(void *arg) {
mutex_data *md = (mutex_data*) arg;
pthread_t tid = pthread_self();
while ((*md).data < INCREMENT_LIMIT) {
// Vérouillage du mutex
pthread_mutex_lock(&(*md).mutex);
(*md).data++;
// Dévérouillage du mutex
pthread_mutex_unlock(&(*md).mutex);
printf("thread [ %ld ] data [ %i ]\n", tid, (*md).data);
// Pause l'exécution du thread pendant 1 seconde
sleep(1);
}
printf("Fin du thread %ld\n", tid);
pthread_exit(NULL);
}
// Fonction principale
int main() {
// Création du mutex
mutex_data md;
// Initialisation de la donnée
md.data = 0;
// Initialisation du mutex
if (pthread_mutex_init(&md.mutex, NULL) != 0) {
printf("\n mutex init failed\n");
return EXIT_FAILURE;
}
// Boucle de création des threads
for (int i = 0; i < NB_THREAD; i++) {
// Création du thread et passage de la structure par référence
int err = pthread_create(&threads[i], NULL, job, &md);
if (err != 0) {
printf("Echec de la création du thread: [%s]", strerror(err));
break;
}
printf("Création du thread numéro %ld\n", threads[i]);
}
// En attente des threads
for (int i = 0; i < NB_THREAD; i++) {
pthread_join(threads[i], NULL);
}
// Destruction du mutex
pthread_mutex_destroy(&md.mutex);
return EXIT_SUCCESS;
}
Un exemple de l'exécution de ce programme donne la sortie suivante :
Création du thread numéro 140530420492032 Création du thread numéro 140530410002176 thread [ 140530410002176 ] data [ 1 ] thread [ 140530420492032 ] data [ 2 ] thread [ 140530410002176 ] data [ 3 ] thread [ 140530420492032 ] data [ 4 ] thread [ 140530410002176 ] data [ 5 ] thread [ 140530420492032 ] data [ 6 ] thread [ 140530420492032 ] data [ 7 ] thread [ 140530410002176 ] data [ 8 ] thread [ 140530420492032 ] data [ 9 ] thread [ 140530410002176 ] data [ 10 ] Fin du thread 140530420492032 Fin du thread 140530410002176