SEG-2506 "Construction de logiciels"

La concurrence  

Lectures supplémentaires:


Concurrence en matériel et logiciel

Concurrence matériel

Un processeur d'un ordinateur exécute une instruction après l'autre. Il n'y a pas de parallélisme. Si nous avons plusieurs processeurs tournants en parallèle (soit à l'intérieur du même ordinateur, soit dans différents ordinateurs) nous avons une des formes du parallélisme ("concurrency" ou "parallelism"). Cette forme de parallélisme réalisée au niveau matériel est appelée parallélisme physique ou concurrence matériel. Le parallélisme physique est lié aux ordinateurs à architecture parallèle. Différentes méthodes ont été inventées pour construire des architectures parallèles (avec plusieurs processeurs) pour exécuter de façon efficace plusieurs tâches en parallèle. On peut classifier ces méthodes ainsi:

Concurrence en logiciel

Dans le manuel, Sebesta distingue les quatre niveaux de concurrence suivants:

Le partage du CPU entre plusieurs threads ou programmes: Chaque système d'exploitation contient un céduleur qui alloue de courtes périodes de temps de CPU aux différents programme actifs dans l'ordinateur. À la fin de chaque période, il détermine quel programme obtiendra le CPU pendant la prochaine période. Un céduleur similaire est inclus dans chaque programme qui supporte à l'intérieur plusieurs threads (unités concurrentes) - ce céduleur déterminera comment la période CPU alloué au programme sera partagée entre les différents threads actifs dans le programme. Par exemple, le programme qui exécute une machine virtuelle Java contient un céduleur pour partager le CPU entre les thread du programme Java (s'il contient des thread en plus du thread main).

Il est à noter que le comportement d'un céduleur n'est pas toujours défini dans toutes les détails, e.g. le céduleur des Threads dans la machine virtuelle qui exécute le byte-code de Java). On distingue généralement les états suivants d'un processus (thread, task ...) en relation avec le céduleur (voir diagramme ci-dessous):

diagram

Dépendences entre processus concurrents

Différents formes de dépendence

Dans le cas le plus simple, des processus parallèles font leur calculs indépendemment les uns des autres. Mais le plus souvent, ils doivent coordonner leurs efforts ce qui est fait par la communication inter-processus et la synchronisation. On peut classifier les interactions comme suit:

Note: Les synchronisations compétitive et coopérative ou la communication en général peuvent être réalisées par des primitives d'échange de messages ou par des variables partagées (incluant des primitives de base pour la synchronisation qui acceptent une opération atomique pour tester et mettre-à-jour une variable ("en même temps"). De telles primitives peuvent être fournies par le système d'exploitation ou par le "hardware" d'un ordinateur a architecture parallèle. Des exemples de primitives de synchronisation par variables partagées sont une instruction machine "test and set" ou le concept de sémaphores décrit dans le manuel section 12.3.

La nature non déterministe des systèmes concurrents (rappeller ce que nous avons discuté dans le chapitre 1-3)

Des processus concurrents peuvent donner lieu à un très grand nombre de séquence d'interactions parce que les exécutions des interactions des différents processus peuvent être intercalées de différentes manières. Dans le cas de processus indépendants (comme par exemple pour des régions parallèles dans un diagramme d'état UML) on suppose (par défault) qu'il n'y a pas de relations entre les processus concurrents (pas de communication, pas de ressources partagées, pas de variables partagées). Ceci a l'avantage que le comportement de chaque processus peut être analysé sans considérer les autres processus. Dans ce cas, le nombre de séquences d'exécution est le plus grand, parce que les actions des différents processus peuvent être intercalées de manière quelconque dans la trace d'exécution globale. Un exemple est montré dans le diagramme ci-dessous, où (a) et (b) représentent deux processus indépendants, définis en forme de LTS, et (c) montre le comportement global résultant (obtenu par analyse d'accessibilité - ce qui est simple dans ce cas-ci). Dans cet exemple, on suppose qu'une transition d'un LTSS ne prend pas de temps, donc l'exécution de deux interactions par deux LTS différents peut être considérée comme étant séquentiel (un après l'autre). Quand deux LTS sont indépendant et une interaction est possible pour chaque LTS, alors leur ordre d'exécution n'est pas déterminé; il y a donc du non-déterminism introduit par la concurrence. Ceci est appelé de la sémantique d'interlacement (interleaving semantics).

concurrency       concurrency

La conclusion principale est le fait que l'on de peut prédire la pas d'exécution qui sera pris par les processus concurrents. Supposant qu'il y a une certaine dépendance entre les deux processus qui amène à certaines difficultés quand la transition z est prise à partir de l'état (3, C), il est très difficile de tester le système pour assurer que cette difficulté est correctement implantée. On ne peut pas contrôler le système pour qu'il prenne un pas particulier. La situation est pire quand on ne sait pas où le problème réside dans l'implantation - les chances d'exécuter la séquence de transitions qui va rencontrer le problème est effectivement très mince lors de tests normaux. Conclusion: Des systèmes concurrents sont très difficile à tester pour vérification. C'est pourquoi on devrait essayer de vérifier de tel systèmes par raisonnement logique.

Quelques notes sur le concept de passages de messages

Un "échange de messages" signifie qu'un processus envoi un message à un autre processus puis continue son propre travail. Le message peut prendre un certain temps pour arriver à l'autre processus. Il peut aussi être stocké dans une file d'attente du processus destinataire si celui-ci n'est pas prêt à le recevoir. Le message est "reçu" seulement quand le processus destinataire est prêt. Ceci décrit un échange de message asynchrone ("asynchronous message passing"), parce que l'envoi et la réception du message ne se font pas en même temps (mais la réception doit avoir lieu après l'envoi !).

L'échange de message asynchrone impose la "synchronisation" suivante entre les deux processus:

Dans le cas de l'échange de message synchrone (par exemple comme réalisé en Ada, v. manuel sections 12.5.1 à 12.5.3), on suppose que l'envoi et la réception de message ont lieu en même temps. On n'a pas besoin d'un "buffer" intermédiaire (file d'attente) dans ce cas. Ceci s'appelle aussi "rendezvous" et suppose une forte synchronisation: l'opération combinée envoi-réception peut seulement se produire si les deux parties (les processus émetteur et récepteur) sont tous les deux prêts à faire leur part. En conséquence, le processus émetteur peut avoir à attendre que le processus récepteur soit prêt et vice-versa (le second cas se produit déjà dans l'échange asynchrone en fait).

Le paradigme d'échange de message est souvent utilisé avec un modèle de machine à états finis pour d'écrire le comportement des processus. Les machines d'états de UML l'utilisent pour la communcation inter-processus (avec un modèle de files sans bornes).

Partage de resources

Besoin de l'exclusion mutuelle

La réservation de resources

Réservation de ressources, e.g. en se servant d'une sémaphore (représentant l'état occupé ou non de la ressource)

Le concept de moniteur

La concurrence en Java (Lire dans le livre de Sebesta)

Exemples:

Questions supplémentaires:


Initialement écrit: 22 mars 2003; mise à jour: 19 mars 2004; révisions typographiques: 2005, 2008, 2009, 2010, 2011. 2013