Beruflich Dokumente
Kultur Dokumente
1 : La programmation multi-threadée
concurrente (en POSIX C)
Contenu :
- l’intérêt du multi-threading
- le problème de concurrence entre threads
- les techniques de synchronisation : mutexs, sémaphores et variables
de condition
Les threads (ou tâches ou fils d'exécution)
Concurrence ou Parallélisme ?
Exemples d’architectures multicoeur
suite
Pourquoi une application multitâche?
q
Pour réduire le temps d’exécution en exploitant plusieurs cœurs
●
exp1: trier d'un tableau de 106 éléments en utilisant n threads,
●
exp2 : déterminer si un entier n est premier ou non
q
Parce que l'application est de nature multitâche
●
exp.: robot qui fait au même temps la reconnaissance de formes,
l'évitement d'obstacles, …
En cas de besoin on peut limiter le blocage à un seul thread
au lieu de stopper toute l'application
q
Pour donner l’illusion d’une exécution parallèle de plusieurs
actions sur un seul cœur.
Exp. : création de 2 threads.
#include <pthread.h>
int main(void) {
pthread_t my_thread1, my_thread2 ;
#include <pthread.h>
int pthread_create(pthread_t * thread, pthread_attr_t
* attr, void *(*Action) (void *), void *arg);
●
La fonction renvoie une valeur de type int : 0 si la création a été réussie ou
une autre valeur si il y a eu une erreur.
●
Les paramètres :
✔
Un pointeur vers une variable pthread_t, dans laquelle l'identifiant
du nouveau thread sera stocké ;
✔
Un pointeur vers un objet d'attribut de thread. Cet objet contrôle les
détails de l'interaction du thread avec le reste du programme.
✔
Un pointeur vers la fonction qui va être exécutée par le thread.
✔
Un argument transmis à la fonction à exécuter.
✔
●
Chaque thread d'un processus a un identifiant de thread:
l
La fonction pthread_self() renvoie l'identifiant du thread en cours.
8
Exp.: création de plusieurs threads
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int main(void){
int i; pthread_t thread[10];
q
La ressource partagée peut être :
✔
une variable globale de type simple
✔
une variable globale de type structure: exp. liste chaînée
✔
une suite d'instructions
✔
un fichier de données
✔
une clé flash USB
q
Solution : Assuerer l'exclusion mutuelle aux ressources partagées à
l'aide des mutexs et des sémaphores
Espace mémoire propre au thread et espace partagé
Les MUTEXs (Mutual EXclusion)
q
Un MUTEX est un objet d’exclusion mutuelle qui sert à gérer l'accès
aux ressources partagées.
C'est l'équivalent d'un jeton de disponibilité
q
Déclaration d'un Mutex:
pthread_mutex_t my_mutex PTHREAD_MUTEX_INITIALIZER
q
Un thread verrouille un Mutex en appelant:
pthread_mutex_lock(my_mutex) ;
(voir PbMultithreadRessourcePartagee_Mutex.c et
PbMultithreadRessourcePartageeFixed_Mutex.c)
Exercice
●
Donnez le pseudo-code de cette solution.
●
Quels sont les deux inconvénients de cette solution ?
Utilisation du Mutex pour protéger une section de code
(demo MultithreadAccesFichier_S1_Mutex.c)
int main(void){
int i; pthread_t thread[10];
srand(time(NULL));
for (i=0 ; i<10 ; i++)
pthread_create(&thread[i], NULL, Tri, (void*)i);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int main(void){
int i; pthread_t thread[10];
srand(time(NULL));
for (i=0 ; i<10 ; i++)
pthread_create(&thread[i], NULL, action, (void*)i);
pthread_mutex_unlock(&my_mutex);
}
pthread_exit(NULL);
}
Pb d'interblocage dû aux MUTEXs
t0 : Ta verrouille le mutex M1
t1 : Tb verrouille le mutex M2
t2 : Ta tente de verrouiller le mutex M2
t3 : Tb tente de verrouiller le mutex M1
q
Un interblocage survient lorsque un ou plusieurs threads sont
bloqués en attendant quelque chose qui n'aura jamais lieu.
q
Un type simple d'interblocage peut survenir lorsqu'un même thread
tente de verrouiller un mutex deux fois d'affilé
Vérification non bloquante d’un mutex
pthread_mutex_trylock(mutex)
●
Appelée sur un mutex déverrouillé, elle verrouille le Mutex comme le ferait
pthread_mutex_lock et renvoie zéro.
●
Si le mutex est déjà verrouillé, elle ne sera pas bloquante et renvoie le
code d'erreur EBUSY.
●
Question: donner l’équivalent du code suivant en utilisant le trylock
au lieu du lock:
i++ ;
pthread_mutex_lock(&M1) ;
j++ ;
Les sémaphores (pour la synchronisation)
q
Un sémaphore est un compteur qui peut être utilisé pour synchroniser
plusieurs threads.
q
Un sémaphore est représenté par une variable de type sem_t :
sem_t S1 ;
q
Un sémaphore supporte deux opérations de base:
•
sem_wait(S1): décrémente la valeur du sémaphore d'une unité.
Mais si la valeur est déjà à zéro, l'opération est bloquante jusqu'à ce que
la valeur du sémaphore redevienne positive
•
sem_post(S1): incrémente la valeur du sémaphore d'une unité.
q
Tout sémaphore doit être initialisé à une valeur x :
sem_init(S1, 0 , x);. // 2ème paramètre tjrs zéro
Différence entre Mutex et Sémaphore
q
Seul le thread qui détient le Mutex peut le libérer, or le sémaphore
peut être incrémenté par un thread et décrémenté par un autre.
q
Un sémaphore peut être initialisé à zéro : aucun jeton au départ.
Exp.
sem_init(&sem1, 0, atoi(argv[1]));
for (i=0 ; i<10 ; i++)
pthread_create(&thread[i], NULL, action, (void*)i);
sem_destroy(&sem1);
}
void* action(void *arg)
{
int num_thread = (int)arg;
int nombre_iterations, i, j, k, n;
nombre_iterations = rand()%8+1;
pthread_exit(NULL);
}
Exemples d'utilisation de sémaphores:
- partage d'un pont de capacité limité
- partage de cinq ponts
- par de cinq ports USB,
- partage d'une connexion réseau
-etc.
Autres fonctions sur les sémaphores
✔
sem_init ( ) : pour initialiser le sémaphore à une valeur initiale;
●
sem_destroy ( ): détruire le sémaphore à la fin
✔
sem_trywait ( ): permettant une mise en attente non bloquante.
✔
sem_getvalue ( ): permettant d'obtenir la valeur courante d'un
sémaphore.
Position du problème:
th_x th_y
O11:x=2 O21:z=4
O12:a= f1(x) ; O22: b= a+z ;
Solution
…
th_x th_y
O11: O21:
O12: a= f1() ; O22: sem_wait (S1) ;
O12_1: sem_post(S1) ; O22_1: b= a+3 ;
O13:c= b+2 O23 :
Les Variables “de condition” (pour la synchronisation)
th_x th_y
Solde= ... ...
if solde < 0 print"Alerte";
???
suite
th_x th_y
O11:solde= ... O21:
O12: if solde < 0 O22: cond_wait (c1) ;
O13 signal (c1) ; O23:print"Alerte";
O14: O24 :
Si O13 est exécutée avant O22 alors O23 ne serait pas exécutée à
moins que C1 serait généré encore une deuxième fois plus tard.
Différence entre sémaphore et variable de condition
- Qd cet appel est exécuté par un th_x, l’OS met le thread en état wait
sur deux points C et M, et l'OS retire le mutex M qui devient libre.
- Qd le signal C est généré, l’OS marque que le thread th_x n’est plus
en attente de C mais seulement de M (si M est déjà pris).
th_x th_y
O11:
O12: solde= .. O21 :...
O13: if solde < 0 { O22:pthread_mutex_lock(&M)
O14: pthread_mutex_lock(&M) O23: pthread_cond_wait (&C1,
O15: pthread_cond_signal(&C1) &M)
O16: pthread_mutex_unlock(&M) O24:pthread_mutex_unlock(&M)
} O25: printf (''alarme'')
O26:...
O17 :….
- En arrivant à sa fin.
- En appelant la fonction
int pthread_exit(void *retval)
ou return()
q
Un thread peut contrôler si et quand il peut être annulé par un autre.
q
Un thread peut réagir de trois façons face à l'annulation:
Il peut être annulable de façon asynchrone. C'est-à-dire il peut être
annulé à n'importe quel moment de son exécution.
Il peut être annulable de façon synchrone. Le thread n'est annulé que
lorsqu'il atteint un certain point de son exécution.
Il peut être impossible à annuler. Les tentatives d'annulation sont
ignorées.
Exp.: rendre une section critique non-annulable
q
Pour interdire aux autres de l’annuler, un thread appelle la fonction:
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE , NULL )
q
Pour indiquer un point annulation, le thread appelle la fonction:
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE , NULL )
Tjs le second argument, s'il n'est pas NULL, est un pointeur vers une
variable qui recevra le type d'annulation précédemment supportée par
le thread