Programmation concurrente
La programmation concurrente désigne la manière dont un programme gère l'exécution simultanée de plusieurs tâches (ou processus) au sein de son flux d'exécution. Les structures de contrôle sont des éléments syntaxiques organisant l'ordre dans lequel les instructions d'un programme sont exécutées, et la programmation concurrente en ajoute une dimension supplémentaire : plusieurs séquences d'instructions peuvent être exécutées en parallèle ou de façon entrelacée.
Définition de la programmation concurrente
La programmation concurrente est un paradigme de programmation dans lequel différentes parties d'un programme sont exécutées "en même temps" ou presque, afin de résoudre des problèmes où plusieurs tâches doivent progresser simultanément. Elle est particulièrement utile lorsque des ressources partagées doivent être utilisées efficacement (par exemple, sur des systèmes multicoeurs) ou lorsque des tâches doivent être réalisées de manière désynchronisée (comme les opérations d'entrée/sortie).
Structures de contrôle dans la programmation concurrente
Voici les principales structures de contrôle associées à la gestion de la concurrence :
- Création et gestion des processus léger : Dans de nombreux langages de programmation, un programme peut créer plusieurs processus léger ou processus pour exécuter des tâches en parallèle. Des structures de contrôle sont utilisées pour gérer la création, la terminaison et la coordination entre ces unités d'exécution.
- Thread thread = new Thread(() -> {
- // code concurrent exécuté dans ce processus léger
- });
- thread.start(); // lancement du processus léger
- process TaskA;begin
- while true do begin
- writeln('Tâche A est exécuté');
- end;
- end;
- Les moniteurs : Les moniteurs sont une structure de contrôle cruciale. Un moniteur est un type abstrait encapsulant des variables et des procédures et gérant l'accès exclusif à des ressources partagées. Seul un processus peut entrer dans un moniteur à un moment donné, ce qui garantit l'intégrité des données partagées.
- monitor SharedResource;
- var
- sresource: integer;
- procedure AccessResource;begin
- sresource:=sresource+1;
- writeln('Ressource accédée, valeur :', sresource);
- end;
- procedure ReleaseResource;begin
- sresource:=sresource-1;
- writeln('Ressource libérée, valeur :', sresource);
- end;
- end.
- Les variables conditionnelles : Les moniteurs incluent des variables conditionnelles, étant des mécanismes utilisés pour synchroniser l'exécution de processus concurrents. Une variable conditionnelle permet à un processus d'attendre qu'une certaine condition soit remplie avant de continuer son exécution.
- monitor Printer;
- var
- printing:boolean;
- condition PrinterFree;
- procedure RequestPrinter;begin
- if printing then
- wait(PrinterFree); (* Attendre que l'imprimante soit libre *)
- printing:=true;
- writeln('Imprimante acquis');
- end;
- procedure ReleasePrinter;begin
- printing := false;
- signal(PrinterFree); (* Signaler que l'imprimante est libre *)
- writeln('Imprimante libérée');
- end;
- end.
- Attente et synchronisation (join, await, wait) : Des structures comme join() en Java ou await en C# permettent de synchroniser les tâches concurrentes, c'est-à-dire de forcer une partie du programme à attendre qu'une autre tâche soit terminée avant de continuer son exécution.
- thread.join(); // Attend que le processus léger se termine
- monitor Printer;
- var
- printing: boolean;
- condition PrinterFree;
- procedure RequestPrinter;begin
- if printing then
- wait(PrinterFree); (* Attendre que l'imprimante soit libre *)
- printing:=true;
- end;
- procedure ReleasePrinter;begin
- printing := false;
- signal(PrinterFree); (* Signaler que l'imprimante est libre *)
- end;
- end.
- process User1;begin
- while true do begin
- RequestPrinter;
- writeln('Utilisateur 1 est en train d''imprimer');
- ReleasePrinter;
- end;
- end;
- process User2;begin
- while true do begin
- RequestPrinter;
- writeln('Utilisateur 1 est en train d''imprimer');
- ReleasePrinter;
- end;
- end;
- Mutex et Sémaphores : Pour protéger l'accès à des ressources partagées (comme des fichiers ou des variables), des mécanismes comme les mutex (verrous) ou les sémaphores sont utilisés. Ce sont des structures de contrôle empêchant plusieurs tâches d'accéder à la même ressource en même temps, afin d'éviter les conflits.
- pthread_mutex_t lock;
- pthread_mutex_lock(&lock);
- /* Section critique */
- pthread_mutex_unlock(&lock);
- Boucles d'événements (Event Loops) : Dans des environnements désynchronisées, comme en JavaScript, la gestion de la concurrence est souvent assurée par des boucles d'événements, gérant l'exécution des callbacks et des promesses (promises) en réponse à des événements.
- Parallélisme implicite (Fork/Join) : Certains langages permettent de définir des tâches étant implicitement exécutées en parallèle sans que le programmeur ait
besoin de créer manuellement des processus légers. Ces langages utilisent des structures de contrôle comme fork et join pour gérer la concurrence.
Exemple en Java (cadre d'application Fork/Join Framework) :
Dans cet exemple, les tâches sont automatiquement découpées et exécutées en parallèle par le cadre d'application.
- Canaux (Channels) : Dans certains langages de programmation comme Go, les canaux sont des structures de contrôle utilisées pour communiquer entre goroutines (les unités d'exécution concurrente en Go) de manière sécurisée.
- c := make(chan int)
- go func() {
- c <- 42 // Envoi d'une donnée à travers le canal
- }()
- value := <-c // Réception de la donnée
Exemple en Java :
Exemple de la création d'un processus en Concurrent Pascal :
Ici, la création d'un processus léger est une structure de contrôle permettant d'exécuter un morceau de code de façon parallèle à d'autres parties du programme.
Exemple de moniteur pour la synchronisation dans Concurrent Pascal :
Ici, SharedResource est un moniteur contrôlant l'accès à une variable partagée sresource. Les processus appelant AccessResource ou ReleaseResource ne peuvent y accéder que de manière séquentielle, garantissant qu'un seul processus à la fois modifie la ressource partagée.
Exemple de variable conditionnelle en Concurrent Pascal :
Dans cet exemple, le moniteur Printer contrôle l'accès à une imprimante. La variable conditionnelle PrinterFree est utilisée pour forcer les processus à attendre (via wait()) que l'imprimante soit libre avant de l'utiliser, et pour notifier (via signal()) lorsque l'imprimante devient disponible.
Exemple en Java :
Cette structure garantit que l'exécution du programme principal ne continuera pas tant que le processus léger thread n'a pas terminé.
Voici un exemple complet où plusieurs processus doivent accéder à une ressource partagée (une imprimante), en utilisant des moniteurs pour synchroniser leur accès en Concurrent Pascal :
Dans cet exemple, User1 et User2 sont deux processus qui essaient d'utiliser l'imprimante. Le moniteur Printer gère l'accès exclusif à l'imprimante à l'aide de la variable printing. Les processus User1 et User2 appellent RequestPrinter pour obtenir l'accès à l'imprimante. Si l'imprimante est déjà utilisée, ils attendent que celle-ci soit libérée grâce à la variable conditionnelle PrinterFree.
Exemple en C (pthreads) :
Ici, pthread_mutex_lock() et pthread_mutex_unlock() sont des structures de contrôle organisant l'accès concurrent à une section critique du code.
Exemple en JavaScript :
Ici, l'utilisation de await est une structure de contrôle permettant au programme d'attendre que la requête soit terminée avant de continuer.
Exemple en Go :
Ici, le canal permet de coordonner l'exécution entre deux goroutines, l'une envoyant des données et l'autre les recevant.
Langage de programmation
Voici quelques langages de programmation gérant les programmes ou processus concurrent :
Langage de programmation | Mot clef |
---|---|
C# | async, await, lock, volatile |
Delphi | ThreadVar |
Java | synchronized, volatile |
Concurrent Pascal | condition, monitor, process, signal, wait |
Mesa | LOCKS, MONITOR, NOTIFY, PROCESS, WAIT |
Cadre d'application ou bibliothèque
Voici quelques cadres d'application ou bibliothèques gérant les programmes ou processus concurrent en fonction des langages de programmation :
Langage de programmation | Cadre d'application, bibliothèque ou module |
---|---|
C# | Akka.NET |
Modula-2 | Processes |
Scala | Akka |
Conclusion
Les structures de contrôle en programmation concurrente permettent de gérer l'exécution simultanée ou entrelacée de tâches tout en assurant la coordination entre elles. Cela inclut des mécanismes comme la création de processus léger, la synchronisation via des primitives comme les mutex ou sémaphores, l'attente d'événements ou l'usage de canaux pour échanger des informations entre différentes unités d'exécution. La clef est de permettre une exécution efficace et correcte des programmes, même dans un environnement où plusieurs tâches doivent progresser en parallèle.
Voir également
Langage de programmation - Mesa - Processus et concurrence