Introduction à la programmation concurrentePremiers pas
Yann Thoma
Institut REDSHaute Ecole d’Ingénierie et de Gestion - VD
Février 2012
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 1 / 48
Plan
Programmation standard
DéfinitionUn processus est une entité active et exécutableouUn processus est un programme en cours d’exécution
Un programme exécute des instructions séquentiellementLe développeur maîtrise la suite des opérationsL’exécution est prévisibleL’exécution est reproductible
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 2 / 48
Plan
Qu’est-ce que la programmation concurrente?
Un processus est décomposé en threadsUn thread est une sorte de processus légerUn thread correspond à une tâche qui s’exécute
Plus ou moins indépendamment des autresExemple:
Un jeu de F1: les voitures sont gérées par des processus différentsUn serveur FTP: chaque client est géré par un thread
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 3 / 48
Plan
Problèmes
Difficulté à synchroniser des tâchesGestion des ressources partagéesProblème de predictibilitéProblème de reproductibilitéConcrètement:
Thérapie par radiation: Therac-25, entre 85 et 87Blackout au Nord-Est des US en 2003
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 4 / 48
Plan
Problèmes - Blackout
Blackout au Nord-Est des US en 2003
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 5 / 48
Plan
Problèmes - Radiations
Thérapie par radiation: Therac-25, 6 accidents entre 85 et 87
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 6 / 48
Plan
Pourquoi utiliser la programmation concurrente?
Ca semble compliqué, alors pourquoi?Optimiser l’utilisation du processeurAugmenter le parallélisme (architecture multi-coeur)
Tout programme faisant du calcul devrait être développé demanière concurrente
Attendre sur plusieurs entréesSimplifier la structure d’un programme (ex. jeu vidéo)Satisfaire des contraintes temporelles
Programmation Temps Réel
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 7 / 48
Plan
Programmation parallèle vs. concurrente
Programmation parallèle (ou répartie)Des processus s’exécutent sur plusieurs processeurs
Programmation concurrenteLes tâches sont gérées par un même processeur
Les mécanismes de synchronisation sont différents
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 8 / 48
Plan
Anatomie d’un processus
Un processus possède entre autreUn code à exécuterUn espace d’adressageUne prioritéUn identifiantUn contexte d’exécution (PC + registres)
Les processus sont gérés par le système d’exploitationPlusieurs processus peuvent s’exécuter en parallèle
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 9 / 48
Plan
Espace d’adressage d’un processus
data segment
stack segment
text segment(code)
Espace d'adressageProcessus
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 10 / 48
Plan
Etats et transitions d’un processus Unix
prêt bloqué
élu
terminé zombie
réveil
préemption
attente
terminaisonterminaison
création
jointure
exécution
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 11 / 48
Plan
Anatomie d’un thread
DéfinitionUn thread est un fil d’exécution dans un processus
Les threads d’un même processus se partagent l’espaced’adressage du processusIls sont ordonnancésIls possèdent
leur propre pileleur propre contexte d’exécution (PC + registres)
Ils ont un cycle de vie semblable à celui d’un processus
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 12 / 48
Plan
Espace d’adressage d’un processus multi-thread
pthread_t threadA;pthread_t threadB;int x;int y;
void fonctionA(void *p){
int i=100;}
void fonctionB(void *p){
int i=10;}
main(){
pthread_create(&threadA,NULL,fonctionA,NULL);pthread_create(&threadB,NULL,fonctionB,NULL);
...}
Espace d'adressageProcessus
stack segment
data segment
text segment
fonctionA()i=100
main()
ThreadAthreadBxy
Pile du threadARegistres________
SPPC...
Attributs________priorité = 2taille = ...
...
Thread ID________
212
threadA
fonctionB()i=10
Pile du threadBRegistres________
SPPC...
Attributs________priorité = 2taille = ...
...
Thread ID________
315
threadB
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 13 / 48
Plan
Thread-processus: en commun
possèdent un ID, un ensemble de registres, un état, et une prioritépossèdent un bloc d’informationpartagent des ressources avec les processus parentssont des entités indépendantes, une fois créésles créateurs de processus et thread ont contrôle sur euxpeuvent changer leurs attributs après création, et créer denouvelles ressourcesne peuvent accéder aux ressources d’autres threads et processusnon reliés
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 14 / 48
Plan
Thread-processus: non commun
les processus ont un espace d’adressage; les threads nonles processus parents et enfants doivent utiliser les mécanismesde communication inter-processus; les threads d’un mêmeprocessus communiquent en lisant et modifiant les variables deleur processusles processus enfants n’ont aucun contrôle sur les autresprocessus enfants; les threads d’un processus sont considéréscomme des pairs, et peuvent exercer un contrôle sur les autresthreads du processusles processus enfants ne peuvent pas exercer de contrôle sur leprocessus parent; n’importe quel thread peut exercer un contrôlesur le thread principal, et donc sur le processus entier
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 15 / 48
Plan
Flot d’exécution d’un processus multi-thread
Thread principal
ThreadA
ThreadB
create
create
com.com.
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 16 / 48
Plan
Flot d’exécution d’un processus multi-thread sur unprocesseur simple coeur
Thread principal
ThreadA
ThreadB
create
create
com.
com.
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 17 / 48
Implémentation
Implémentation
Comment implémenter la concurrence?Grâce à des appels systèmesGrâce à des mécanismes du langage de programmationSolution intermédiaire
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 18 / 48
Implémentation
1. Grâce à des appels systèmes
Le système d’exploitation offre une bibliothèque appelée systèmemulti-tâcheAvantage:
Un langage quelconque peut profiter de la bibliothèqueDésavantages:
Un système multi-tâche doit être fourni par le systèmeLa portabilité (dépendance au système cible)Déboguage délicatPas de méthodologie de programmation imposée
Exemple: langage C avec bibliothèque Pthread
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 19 / 48
Implémentation
Grâce au langage
Les notions concurrentes sont données par le langageDe même que les constructionsDétection des erreurs à la compilationMéthodologie de programmation imposée par le langageExemple: Ada
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 20 / 48
Implémentation
Solution intermédiaire
Un précompilateur gère l’implémentation des outilsExemple: C++ avec librairie Qt
Code indépendant de la plateforme cible
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 21 / 48
Implémentation
Bibliothèque Pthread
Bibliothèque normalisée POSIX (ISO/IEC 9945-1:1996)Existe sous Linux et ses dérivés, et sous Windows (installationdepuis http://sourceware.org/pthreads-win32/)Fichier d’en-tête :pthread.h
Compilation et édition des liens :gcc app.c -lpthread
ougcc -D_REENTRANT app.c -lpthread
Déclaration d’un threadpthread_t thread;
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 22 / 48
Implémentation
Création d’un thread
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg);
thread est un pointeur sur une variable de type pthread_t
attr permet de définir les attributs du threadstart_routine correspond à la fonction qui est exécutée par lethread créé. La fonction exécutée par le thread créé devra avoir leprototype suivant:
void *fonction(void *data);
arg est un pointeur passé à la fonction
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 23 / 48
Implémentation
Jointure
int pthread_join(pthread_t thread,void **value_ptr);
Attend que la tâche en paramètre se terminevalue_ptr contient la valeur de retour de la tâche
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 24 / 48
Implémentation
Exemple (1)
typedef struct {int a;int b;
} struct_t;
void *Tache1(void *arg) {struct_t *var;var = (struct_t *)arg;printf("Tache1: a=%d, b=%d\n",var->a,var->b);return NULL;
}
void *Tache2(void *arg) {int i = (int)arg;printf("Tache2: i=%d\n",i);return NULL;
}
int main(int argc,char *argv[]){
struct_t v;v.a = 1; v.b = 2;pthread_t thread;pthread_create(&thread,
NULL,Tache1,&v);
pthread_join(thread,NULL);pthread_create(&thread,
NULL,Tache2,(void *)2);
pthread_join(thread,NULL);return EXIT_SUCCESS;
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 25 / 48
Implémentation
Exemple (2)
typedef struct {int a;int b;
} struct_t;
void *Tache1(void *arg) {struct_t *var;var = (struct_t *)arg;printf("Tache1: a=%d, b=%d\n",var->a,var->b);return NULL;
}
void *Tache2(void *arg) {int i = (int)arg;printf("Tache2: i=%d\n",i);return NULL;
}
int main(int argc,char *argv[]){
struct_t v;v.a = 1; v.b = 2;pthread_t thread1,thread2;pthread_create(&thread1,
NULL,Tache1,&v);
pthread_create(&thread2,NULL,Tache2,(void *)2);
pthread_join(thread1,NULL);pthread_join(thread2,NULL);return EXIT_SUCCESS;
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 26 / 48
Séparation d’un programme en plusieurs threads
Séparation d’un programme en threads
Comment décomposer un programme en plusieurs threads?Il existe plusieurs modèles
Le modèle délégation (boss-worker model ou delegation model enanglais)Le modèle pair (peer model en anglais)Le modèle pipeline (pipeline model en anglais)
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 27 / 48
Séparation d’un programme en plusieurs threads
Modèle délégation
Un thread principalDes threads travailleurs
tâche1
tâche2
tâche3
entrée
patron
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 28 / 48
Séparation d’un programme en plusieurs threads
Modèle délégation: exemple 1
void *patron(void *) {boucle infinie {attend une requêteswitch (requete) {case requeteX: pthread_create( ... tacheX); break;case requeteY: pthread_create( ... tacheY); break;...
}}
}
void *tacheX(void *) {exécuter le travail demandé, puis se terminer
}
void *tacheY(void *) {exécuter le travail demandé, puis se terminer
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 29 / 48
Séparation d’un programme en plusieurs threads
Modèle délégation: exemple 2
void *patron(void *) {// crée tous les threadspthread_create(...);boucle infinie {attend une requête;place la requête dans la file d’attentesignale aux travailleurs qu’une requête est prête
}}
void *travailleur(void *) {boucle infinie {bloque jusqu’à être activé par le patronrécupère la requête de la file d’attenteswitch(requete){case requeteX: tacheX();case requeteY: tacheY();...
}}
}
void tacheX() {exécuter le travail demandé
}
void tacheY() {exécuter le travail demandé
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 30 / 48
Séparation d’un programme en plusieurs threads
Modèle pair
Pas de thread principalTous égauxChacun s’arrange avec ses entrées/sorties
tâche1
tâche2
tâche3
entrées
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 31 / 48
Séparation d’un programme en plusieurs threads
Modèle pair: exemple
main() {pthread_create( ... tache1);pthread_create( ... tache2);...signale aux threads qu’ils peuvent commencer à travailler
}
tache1() {attend le signal de commencementeffectue le traitement, et synchronise avec les autres threadssi nécessaire
}
tache2() {attend le signal de commencementeffectue le traitement, et synchronise avec les autres threadssi nécessaire
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 32 / 48
Séparation d’un programme en plusieurs threads
Modèle pipeline
Appliqué lorsque:L’application traite une longue chaîne d’entrée;Le traitement à effectuer sur ces entrée peut être décomposé ensous-tâches (étages de pipeline) au travers desquelles chaquedonnée d’entrée doit passer;Chaque étage peut traiter une donnée différente à chaque instant.
Un thread attend les données du précédentEt les transmet ensuite au suivant
tâche1 tâche2 tâche3
entrée sortie
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 33 / 48
Séparation d’un programme en plusieurs threads
Modèle pipeline: exemple (1)
etage1() {boucle infinie {récupérer une entrée du programmetraiter cette donnéepasser le résultat à l’étage suivant
}}etage2() {
boucle infinie {récupérer une donnée de l’étage précédenttraiter cette donnéepasser le résultat à l’étage suivant
}}etageN() {
boucle infinie {récupérer une donnée de l’étage précédenttraiter cette donnéepasser le résultat en sortie du programme
}}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 34 / 48
Séparation d’un programme en plusieurs threads
Modèle pipeline: exemple (2)
main() {pthread_create( ... etage1);pthread_create( ... etage2);...pthread_create( ... etageN);...
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 35 / 48
Terminaison
Terminaison: options
La terminaison d’un thread peut être exécutée depuis:Le thread lui-même:
returnpthread_exit()
Un autre thread:pthread_cancel()
Mal terminer un thread peut laisser le système dans un étatincohérent!!
Plus spécifiquement depuis un autre thread
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 36 / 48
Terminaison
Auto-terminaison
return value;Attention avec le thread principal: Terminaison du programme!!
void pthread_exit(void *value)La fonction met fin au thread, et retourne value au threadattendant grâce à une jointure (pthread_join()).
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 37 / 48
Terminaison
Auto-terminaison: exemple (1)
void *tache1(void *arg) {printf("Tâche 1\n");return 3;
}void *tache2(void *arg) {
printf("Tâche 2\n");pthread_exit(4); Dans quel état se trouve le thread ensuite?
}int main(int argc,char *argv[]) {
pthread_t thread1;pthread_t thread2;pthread_create(&thread1,NULL,tache1,NULL);pthread_create(&thread2,NULL,tache2,NULL);void *statut1;void *statut2;pthread_join(thread1,&statut1);pthread_join(thread2,&statut2);printf("Statut1: %d, Status2: %d\n",
statut1,statut2);return EXIT_SUCCESS; Attention
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 38 / 48
Terminaison
Auto-terminaison: exemple (2)
void *tache1(void *arg) {printf("Tâche 1\n");return 3;
}void *tache2(void *arg) {
printf("Tâche 2\n");pthread_exit(4);
}int main(int argc,char *argv[]) {
pthread_t thread1;pthread_t thread2;pthread_create(&thread1,NULL,tache1,NULL);pthread_create(&thread2,NULL,tache2,NULL);void *statut1;void *statut2;pthread_join(thread1,&statut1);pthread_join(thread2,&statut2);printf("Statut1: %d, Status2: %d\n",
statut1,statut2);pthread_exit(NULL); Attention
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 39 / 48
Terminaison
Annulation par un autre thread
int pthread_cancel(pthread_t thread);
thread est un pointeur sur une variable de type pthread_t
Cette fonction permet de faire se terminer un thread depuis unautreLa terminaison s’effectue sur un point d’annulation
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 40 / 48
Terminaison
Politique d’annulation (1)
Le thread annulé peut définir sa politique d’annulation
int pthread_setcancelstate(int state,int *oldstate);
state peut prendre les valeurs:PTHREAD_CANCEL_ENABLE: Autorise l’annulationPTHREAD_CANCEL_DISABLE: Interdit l’annulation
oldstate contient ensuite l’ancienne valeur d’enable
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 41 / 48
Terminaison
Politique d’annulation (2)
Si le thread autorise l’annulation, il est possible de définir son typed’annulation
int pthread_setcanceltype(int type,int *oldtype);
type peut prendre les valeurs:PTHREAD_CANCEL_DEFERRED: Autorise l’annulation en des pointprécisPTHREAD_CANCEL_ASYNCHRONOUS: Autorise l’annulationn’importe quand
oldtype contient ensuite l’ancien type d’annulation
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 42 / 48
Terminaison
Point d’annulation
Un thread peut placer ses points d’annulations
void pthread_testcancel(void);
L’annulation est donc permise en ces points
Attention, certains appels système sont des pointsd’annulation
Exemple: write(), utilisé par printf()
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 43 / 48
Terminaison
Exemple (1)
int counter1 = 1;
void *tache1(void *arg) {int ancien_etat, ancien_type;pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&ancien_etat);pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&ancien_type);while (true) {
counter1++;if (counter1 % 100 == 0)
pthread_testcancel();}
}
int main(int argc,char *argv[]) {pthread_t thread1;pthread_create(&thread1,NULL,tache1,NULL);void *statut1;usleep(50);pthread_cancel(thread1);pthread_join(thread1,&statut1);printf("Counter1: %d\n",counter1);return EXIT_SUCCESS;
}Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 44 / 48
Terminaison
Exemple (2)
int counter1 = 1;
void *tache1(void *arg) {int ancien_etat, ancien_type;pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&ancien_etat);pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&ancien_type);while (true) {
counter1++;}
}
int main(int argc,char *argv[]) {pthread_t thread1;pthread_create(&thread1,NULL,tache1,NULL);void *statut1;usleep(50);pthread_cancel(thread1);pthread_join(thread1,&statut1);printf("Counter1: %d\n",counter1);return EXIT_SUCCESS;
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 45 / 48
Terminaison
Exemple (3)
int counter1 = 1;
void *tache1(void *arg) {int ancien_etat, ancien_type;pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&ancien_etat);pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,&ancien_type);while (true) {
counter1++;printf("counter1: %d\n");
}}
int main(int argc,char *argv[]) {pthread_t thread1;pthread_create(&thread1,NULL,tache1,NULL);void *statut1;usleep(50);pthread_cancel(thread1);pthread_join(thread1,&statut1);printf("Counter1: %d\n",counter1);return EXIT_SUCCESS;
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 46 / 48
Terminaison
Exemple (4)
int counter1 = 1;
void *tache1(void *arg) {int ancien_etat, ancien_type;pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&ancien_etat);pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&ancien_type);while (true) {
counter1++;}
}
int main(int argc,char *argv[]) {pthread_t thread1;pthread_create(&thread1,NULL,tache1,NULL);void *statut1;usleep(50);pthread_cancel(thread1);pthread_join(thread1,&statut1);printf("Counter1: %d\n",counter1);return EXIT_SUCCESS;
}
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 47 / 48
Terminaison
Exemple (5)
Soit le thread tache1 pouvant être annulé par un autre
void *tache1(void *arg) {int ancien_etat, ancien_type;pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,&ancien_etat);pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,&ancien_type);// traitement divers
FILE f=fopen(...);fprintf(f, ...);fprintf(f, ...);fclose(f);
// traitement divers}
Que peut-il se passer?Que faire pour y remédier?
Yann Thoma (HES-SO - HEIG-VD - REDS) Introduction à la programmation concurrente Février 2012 48 / 48