Article de reference

Synchronisation (informatique)

En informatique , la synchronisation est la tâche qui consiste à coordonner plusieurs processus pour qu'ils se rejoignent ou se coordonnent à un certain point, afin de parvenir ...

informatique , la synchronisation est la tâche qui consiste à coordonner plusieurs processus pour qu'ils se rejoignent ou se coordonnent à un certain point, afin de parvenir à un accord ou de s'engager dans une certaine séquence d'actions.

Bifurcation et jonction : Lorsqu'une tâche atteint un point de bifurcation, elle est divisée en N sous-tâches, chacune étant traitée par n tâches. Une fois traitée, chaque sous-tâche attend que toutes les autres aient terminé leur traitement. Elles sont ensuite regroupées et quittent le système. La programmation parallèle nécessite donc une synchronisation, car tous les processus parallèles attendent la fin de plusieurs autres processus.

Relation producteur-consommateur : Dans une relation producteur-consommateur, le processus du consommateur dépend du processus du producteur jusqu’à ce que les données nécessaires aient été produites.

Ressources à usage exclusif : lorsqu’une ressource dépend de plusieurs processus et qu’ils doivent y accéder simultanément, le système d’exploitation doit s’assurer qu’un seul processeur y accède à la fois. Cela réduit la concurrence.

Figure 1 : Trois processus accédant simultanément à une ressource partagée ( section critique ).

La synchronisation des threads est un mécanisme qui garantit que deux ou plusieurs processus ou threads concurrents n'exécutent pas simultanément un segment de programme particulier, appelé section critique . L'accès des processus à la section critique est contrôlé par des techniques de synchronisation. Lorsqu'un thread commence l'exécution de la section critique (segment sérialisé du programme), les autres threads doivent attendre la fin de son exécution. En l' absence de techniques de synchronisation appropriées , une condition de concurrence peut survenir , entraînant des valeurs imprévisibles pour les variables, qui peuvent varier en fonction du moment des changements de contexte des processus ou des threads.

Par exemple, supposons qu'il existe trois processus, nommés 1, 2 et 3. Ces trois processus s'exécutent simultanément et doivent partager une ressource commune (section critique), comme illustré sur la figure 1. La synchronisation est nécessaire pour éviter tout conflit d'accès à cette ressource partagée. Ainsi, lorsque les processus 1 et 2 tentent d'accéder à cette ressource, celle-ci ne doit être attribuée qu'à un seul processus à la fois. Si elle est attribuée au processus 1, le processus 2 doit attendre que le processus 1 libère la ressource (comme illustré sur la figure 2).

Figure 2 : Un processus accédant à une ressource partagée si disponible, sur la base d'une technique de synchronisation.

Une autre exigence de synchronisation à prendre en compte est l'ordre d'exécution des processus. Par exemple, on ne peut pas embarquer dans un avion sans avoir acheté son billet. De même, on ne peut pas consulter ses courriels sans avoir validé ses identifiants (nom d'utilisateur et mot de passe). De la même façon, un distributeur automatique de billets ne fonctionnera pas tant qu'il n'aura pas saisi un code PIN correct.

Outre l'exclusion mutuelle, la synchronisation traite également des points suivants :

  • Un blocage (ou interblocage ) survient lorsque plusieurs processus attendent une ressource partagée (section critique) détenue par un autre processus. Dans ce cas, les processus restent bloqués et n'exécutent plus rien.
  • la famine , qui se produit lorsqu'un processus attend d'entrer dans la section critique, mais que d'autres processus monopolisent la section critique, et que le premier processus est forcé d'attendre indéfiniment ;
  • L'inversion de priorité se produit lorsqu'un processus à haute priorité se trouve dans la section critique et qu'il est interrompu par un processus à priorité moyenne. Cette violation des règles de priorité peut survenir dans certaines circonstances et entraîner de graves conséquences dans les systèmes temps réel.
  • L'attente active se produit lorsqu'un processus interroge fréquemment une section critique pour déterminer s'il y a accès. Ces interrogations fréquentes absorbent du temps de traitement qui pourrait être alloué à d'autres processus.

Minimisation

L'un des défis de la conception d'algorithmes exascale est de minimiser la synchronisation. Celle-ci est plus chronophage que le calcul lui-même, notamment en calcul distribué. La réduction de la synchronisation préoccupe les informaticiens depuis des décennies. Ce problème prend une importance croissante ces dernières années, à mesure que l'écart entre les gains de puissance de calcul et la latence s'accroît. Des expériences ont montré que les communications (globales) dues à la synchronisation sur les ordinateurs distribués représentent une part prépondérante du temps de calcul dans un solveur itératif pour matrices creuses . Ce problème suscite un intérêt grandissant depuis l'émergence d'une nouvelle métrique de référence, le gradient conjugué haute performance (HPCG) , permettant de classer les 500 supercalculateurs les plus performants.

Problèmes

Voici quelques problèmes classiques de synchronisation :

Ces problèmes servent à tester presque tous les schémas de synchronisation ou primitives nouvellement proposés.

Aérien

Les surcoûts de synchronisation peuvent avoir un impact significatif sur les performances dans les environnements de calcul parallèle , où la fusion de données provenant de plusieurs processus peut engendrer des coûts considérablement plus élevés — souvent de deux ordres de grandeur ou plus — que le traitement des mêmes données sur un seul thread, principalement en raison des surcoûts supplémentaires liés aux mécanismes de communication et de synchronisation interprocessus .

Synchronisation matérielle

De nombreux systèmes offrent un support matériel pour le code des sections critiques .

Un système monoprocesseur peut désactiver les interruptions en exécutant le code en cours sans préemption , ce qui est très inefficace sur les systèmes multiprocesseurs . « La capacité essentielle pour implémenter la synchronisation dans un système multiprocesseur est un ensemble de verrous et les barrières . En général, les architectes ne s'attendent pas à ce que les utilisateurs manipulent directement les primitives matérielles de base, mais plutôt à ce que les programmeurs système les utilisent pour créer une bibliothèque de synchronisation, un processus souvent complexe et délicat. » De nombreux matériels modernes fournissent de telles instructions atomiques, deux exemples courants étant : test-and-set , qui opère sur un seul mot de mémoire, et compare-and-swap , qui échange le contenu de deux mots de mémoire.

Assistance en langages de programmation

En Java , une façon d'éviter les interférences entre threads et les erreurs de cohérence mémoire consiste à préfixer la signature d'une méthode par le mot-clé ` synchronized` . Dans ce cas, le verrou de l'objet déclarant est utilisé pour garantir la synchronisation. Une autre méthode consiste à encapsuler un bloc de code dans une section `synchronized(someObject){...}` , offrant ainsi un contrôle plus fin. Cela oblige tout thread à acquérir le verrou de `someObject` avant de pouvoir exécuter le bloc qu'il contient. Le verrou est automatiquement libéré lorsque le thread qui l'a acquis quitte ce bloc ou entre en état d'attente à l'intérieur de celui-ci. Toute modification de variable effectuée par un thread dans un bloc `synchronized` devient visible par les autres threads lorsqu'ils acquièrent également le verrou et exécutent le bloc. Quelle que soit l'implémentation, n'importe quel objet peut servir de verrou, car tous les objets Java possèdent un verrou intrinsèque ou un verrou de surveillance associé lors de leur instanciation.

Les blocs synchronisés Java , en plus de permettre l'exclusion mutuelle et la cohérence de la mémoire, permettent la signalisation : l'envoi d'événements depuis les threads ayant acquis le verrou et exécutant le bloc de code vers ceux qui attendent ce verrou au sein du bloc. Les sections synchronisées Java combinent donc les fonctionnalités des mutex et des événements pour garantir la synchronisation. Un tel mécanisme est appelé moniteur de synchronisation .

Le framework .NET utilise également des primitives de synchronisation. « La synchronisation est conçue pour être coopérative, exigeant que chaque thread suive le mécanisme de synchronisation avant d'accéder aux ressources protégées pour des résultats cohérents. Le verrouillage, la signalisation, les types de synchronisation légers, l'attente active et les opérations interverrouillées sont des mécanismes liés à la synchronisation dans .NET. »

De nombreux langages de programmation prennent en charge la synchronisation et des langages entièrement spécialisés ont été écrits pour le développement d'applications embarquées où une synchronisation strictement déterministe est primordiale.

Mise en œuvre

Verrous à rotation

les architectures pilotées par les événements , les transactions synchrones peuvent être réalisées en utilisant le paradigme requête-réponse et peuvent être mises en œuvre de deux manières :

Fondements mathématiques

La synchronisation était à l'origine un concept basé sur les processus, permettant d'obtenir un verrou sur un objet. Son principal usage concernait les bases de données. Il existe deux types de verrous (de fichiers) : les verrous en lecture seule et les verrous en lecture-écriture. Les verrous en lecture seule peuvent être obtenus par plusieurs processus ou threads. Les verrous en lecture-écriture sont exclusifs, car ils ne peuvent être utilisés que par un seul processus ou thread à la fois.

Bien que les verrous aient été conçus pour les bases de données de fichiers, les données sont également partagées en mémoire entre les processus et les threads. Il arrive que plusieurs objets (ou fichiers) soient verrouillés simultanément. S'ils ne le sont pas, ils peuvent se chevaucher, provoquant une exception de blocage.

Java et Ada n'ont que des verrous exclusifs car ils sont basés sur les threads et reposent sur l' instruction processeur de comparaison et d'échange .

Le monoïde d'histoire fournit un fondement mathématique abstrait aux primitives de synchronisation . De nombreux outils théoriques de plus haut niveau, tels que les calculs de processus et les réseaux de Petri , peuvent également être construits à partir de ce monoïde.

Exemples

Voici quelques exemples de synchronisation en fonction de différentes plateformes.

Sous Windows

Windows fournit :

Sous Linux

Linux fournit :

L'activation et la désactivation de la préemption du noyau ont remplacé les verrous d'attente active sur les systèmes monoprocesseurs. Avant la version 2.6 du noyau, Linux désactivait les interruptions pour implémenter des sections critiques courtes. Depuis la version 2.6, Linux est entièrement préemptif.

Dans Solaris

Solaris fournit :

Dans Pthreads

Pthreads est une API indépendante de la plateforme qui fournit :

  • mutex ;
  • variables de condition ;
  • verrous lecteurs-écrivains ;
  • verrous de rotation ;
  • barrières .