Horloges Logiques, Algorithme de Lamport et Exclusion Mutuelle

La gestion du temps et de l’ordre des événements dans les systèmes distribués est un pilier essentiel de l’informatique moderne, particulièrement pour assurer la cohérence et la fiabilité sans horloge centralisée. Leslie Lamport, un informaticien visionnaire, a révolutionné ce domaine avec ses horloges logiques et son algorithme d’exclusion mutuelle. Introduits dans son article seminal de 1978, ces concepts ont permis de modéliser l’ordonnancement causal des événements et de résoudre des problèmes comme l’accès concurrents aux ressources partagées dans des environnements distribués. Cet article explore les horloges logiques, l’algorithme de Lamport pour l’exclusion mutuelle, son aspect mathématique et une implémentation en C++.

Horloges physiques & logiques

Les horloges physiques synchronisent le temps entre les nœuds d’un système distribué en partageant des références temporelles locales. Typiquement basées sur des horloges à quartz (±15 secondes de dérive par mois), elles coordonnent des opérations comme l’envoi de messages. Cependant, elles ne reflètent pas les relations causales entre événements, une lacune comblée par les horloges logiques.

Les horloges logiques, introduites par Lamport, établissent un ordre partiel basé sur la relation 's’est produit avant' ou 'happened-before': 'concept de temps utilisé dans les systèmes distribués pour enregistrer l'ordre des événements. Généralement représenté par un entier croissant, distinct des horloges physiques comme le temps du système, et est déterminé en fonction des relations causales entre les événements'. Si un événement A cause un événement B (A → B), son horodatage logique est inférieur. Deux types principaux existent : les horloges de Lamport, qui utilisent un compteur scalaire, et les horloges vectorielles, qui offrent une vision plus fine de la causalité via des vecteurs (structure de données utilisée dans les systèmes distribués pour suivre l'ordre des événements sur plusieurs nœuds ou processus).

happened-before

Les Horloges de Lamport

Les horloges de Lamport assignent à chaque événement un horodatage logique pour garantir un ordonnancement causal. Chaque processus maintient un compteur Ci qui s’incrémente à chaque événement interne. Lorsqu’un message est envoyé, le compteur est inclus, et le récepteur ajuste son propre compteur pour dépasser à la fois sa valeur actuelle et l’horodatage reçu. Formellement :

  • Pour un événement interne, Ci = Ci + 1.
  • Pour un message envoyé, inclure Ci.
  • À la réception, Ci = max(Ci, Cm) + 1, où Cm est l’horodatage du message.

Si A → B, alors C(A) < C(B). Cela garantit un ordre partiel, bien que les événements concurrents puissent nécessiter des horloges vectorielles pour une distinction claire.

Bien que les horloges de Lamport fournissent un ordre partiel pour les événements liés, elles échouent à différencier les opérations concurrentes. Les horloges vectorielles résolvent cela avec une liste de N compteurs (N étant le nombre de processus), incrémentée localement et fusionnée par maximum lors des échanges. Cela offre une causalité précise, bien que la mémoire croisse linéairement avec N. Des variantes optimisées atténuent ce problème, élargissant leur applicabilité.

lamport & vector clocks

L’Algorithme de Lamport pour l’exclusion mutuelle

En plus des horloges logiques, Lamport a proposé un algorithme pour l’exclusion mutuelle dans les systèmes distribués, permettant à des processus d’accéder à une ressource critique sans conflit. Publié dans 'Time, Clocks, and the Ordering of Events in a Distributed System' (1978), il repose sur les horodatages logiques pour ordonner les demandes d’accès.

L’algorithme suit ces étapes fondamentales :

  • Demande : Un processus Pi souhaitant accéder à la ressource critique envoie une requête avec son horodatage Ti à tous les autres processus et se place dans une file d’attente locale.
  • Réception : Un processus Pj recevant la requête l’ajoute à sa file et envoie un accusé de réception à Pi.
  • Accès : Pi entre en section critique si :
    1. Il a reçu un accusé de tous les autres processus avec des horodatages postérieurs à Ti.
    2. Sa requête est la première dans sa file locale selon l’ordre des horodatages (et l’identifiant du processus en cas d’égalité).
  • Libération : Après avoir utilisé la ressource, Pi envoie un message de libération à tous, permettant aux autres de mettre à jour leurs files.

Aspect mathematique

Dans son article original, Lamport définit la relation « happened-before » (→) comme suit :

  • Si A et B sont des événements dans le même processus et A précède B, alors A → B.
  • Si A est l’envoi d’un message et B sa réception, alors A → B.
  • Si A → B et B → C, alors A → C (transitivité).

Les horodatages respectent : si A → B, alors C(A) < C(B). L’ordre total est obtenu en combinant les horodatages avec les identifiants des processus : pour deux requêtes (Ti, Pi) et (Tj, Pj), on a (Ti, Pi) < (Tj, Pj) si Ti < Tj ou (Ti = Tj et Pi < Pj).

Implémentation en C++

Voici une implémentation d’une horloge vectorielle, une extension des horloges de Lamport, en C++ :


#include <iostream>
#include <vector>
#include <algorithm>

class VectorClock {
private:
    int id;                // Identifiant du processus
    std::vector<int> clock; // Vecteur d’horloges

public:
    VectorClock(int numProcesses, int processId) : id(processId), clock(numProcesses, 0) {}

    // Incrémente l’horloge locale
    void incrementClock() {
        clock[id]++;
    }

    // Envoie un message avec l’horloge actuelle
    void sendMessage(VectorClock& receiver) {
        incrementClock();
        std::cout << "Processus " << id << " envoie un message avec l’horloge vectorielle : ";
        for (int c : clock) std::cout << c << " ";
        std::cout << "au processus " << receiver.id << std::endl;
        receiver.receiveMessage(clock);
    }

    // Reçoit un message et met à jour l’horloge
    void receiveMessage(const std::vector<int>& senderClock) {
        for (size_t i = 0; i < clock.size(); i++) {
            clock[i] = std::max(clock[i], senderClock[i]);
        }
        incrementClock();
        std::cout << "Processus " << id << " a reçu un message. Nouvelle horloge : ";
        for (int c : clock) std::cout << c << " ";
        std::cout << std::endl;
    }
};

int main() {
    VectorClock vc1(2, 0); // Processus 0
    VectorClock vc2(2, 1); // Processus 1
    vc1.sendMessage(vc2);  // Processus 0 envoie un message à Processus 1
    vc2.sendMessage(vc1);  // Processus 1 répond à Processus 0
    return 0;
}

Cette implémentation illustre la synchronisation des horloges entre deux processus, garantissant un suivi précis des relations causales.

vector logical clocks

N.B

Un problème avec les horloges vectorielles est que la mémoire requise augmente linéairement avec le processus. Mais, elles fournissent un moyen de raisonner sur l'ordre des événements dans les systèmes distribués, assurant la cohérence et la précision des données.

Défis et Limites

Les horloges de Lamport et leur algorithme d’exclusion mutuelle présentent des défis :

  • Évolutivité : L’échange intensif de messages augmente avec le nombre de processus.
  • Tolérance aux pannes : Une défaillance d’un nœud peut bloquer le système.
  • Concurrence : Les événements simultanés nécessitent des outils comme les horloges vectorielles.

Ces limitations soulignent une critique plus large: la complexité des systèmes distribués provient souvent de notre dépendance aux horloges et aux processus critiques. La cohérence distribuée, en soi, est simple; ordonner les événements et rejeter les contradictions mais elle se complique lorsque chaque processus devient une ressource centrale, sensible aux défaillances en cascade. En abandonnant les horloges au profit de la causalité, via des machines d’état déterministes et Paxos, on peut réduire cette complexité.

Applications Pratiques

Ces concepts ont des applications concrètes :

  • Bases de données distribuées : Ordonnancement des transactions.
  • Consensus (Paxos) : Accord fiable malgré les pannes.
  • Cloud computing : Synchronisation des opérations géodistribuées.
  • Systèmes temps réel : Coordination dans la robotique ou les véhicules autonomes.
  • Bitcoin protocol: Bitcoin agit comme une horloge immuable dans un système distribué, ordonnant les événements via des blocs horodatés qui forment une chaîne linéaire de causalité. Chaque bloc, un 'battement de cœur' numérique, enregistre les transactions (∆a, ∆b) dans un ordre parfait, ancrant une réalité partagée sans horloge globale. En supposant qu’un message atteignant une supermajorité de nœuds honnêtes est confirmé, et que la validité de ∆b dépend de ∆a précédemment validé, Bitcoin utilise une variante des horloges logiques. Les nœuds (A, B, C) maintiennent des états (f) évoluant avec des compteurs inspirés des horloges de Lamport: A passe de f(ø, 1) à f(∆a, 1) en recevant ∆a, puis à f(∆a, 2) en synchronisant avec B, tandis que B avance à f(∆b, 2) pour ∆b. La causalité est suivie via des arbres de Merkle, assurant qu’une transaction antérieure (∆a) à un « tour » d’horloge inférieur reste valide face à la pointe actuelle de la chaîne. Ainsi, Bitcoin fusionne horloges logiques et consensus pour une chronologie digitale persistante.

Conclusion

Les travaux de Lamport sur les horloges logiques et l’exclusion mutuelle ont transformé les systèmes distribués, offrant des outils pour gérer le temps et la concurrence. Malgré leurs limites, ils restent fondamentaux pour des technologies modernes, et les recherches continuent d’affiner ces concepts pour relever les défis futurs.

​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

Comments

Popular posts from this blog

Refactorisation de Code avec les 10 Règles de la NASA

Les expressions régulières gourmandes & non gourmandes en C++

MANIFESTO DU DÉVELOPPEUR/PROGRAMMEUR TOGOLAIS