CodeGym /Blog Java /Random-FR /Différence entre un mutex, un moniteur et un sémaphore
Auteur
Oleksandr Miadelets
Head of Developers Team at CodeGym

Différence entre un mutex, un moniteur et un sémaphore

Publié dans le groupe Random-FR
Salut! Lorsque vous avez étudié le multithreading sur CodeGym, vous avez fréquemment rencontré les concepts de « mutex » et de « moniteur ». Sans jeter un coup d'œil, pouvez-vous dire en quoi ils diffèrent? :) Si oui, bravo ! Si ce n'est pas le cas (ce qui est le plus courant), ce n'est pas une surprise. "Mutex" et "moniteur" sont en fait des concepts liés. De plus, lorsque vous lisez des leçons et regardez des vidéos sur le multithreading sur d'autres sites Web, vous rencontrerez un autre concept similaire : "sémaphore". Il a également une fonction très similaire aux moniteurs et aux mutex. C'est pourquoi nous allons étudier ces trois termes. Nous allons examiner quelques exemples et arriver à une compréhension définitive de la façon dont ces concepts diffèrent les uns des autres :)

Mutex

Un mutex (ou verrou) est un mécanisme spécial de synchronisation des threads. L'un est "attaché" à chaque objet en Java — vous le savez déjà :) Peu importe que vous utilisiez des classes standard ou que vous créiez vos propres classes, par exemple Cat et Dog : tous les objets de toutes les classes ont un mutex. Le terme "mutex" vient de "MUTual EXclusion", ce qui décrit parfaitement son objectif. Comme nous l'avons dit dans une de nos leçons précédentes, un mutex permet de s'assurer qu'un seul thread à la fois a accès à l'objet. Un exemple réel populaire d'un mutex implique des toilettes. Lorsqu'une personne entre dans une cloison de toilettes, elle verrouille la porte de l'intérieur. Les toilettes sont comme un objet accessible par plusieurs threads. La serrure sur la porte de la cloison est comme un mutex, et la ligne de personnes à l'extérieur représente des fils. Le verrou de la porte est le mutex des toilettes : il garantit qu'une seule personne peut entrer. Quelle est la différence entre un mutex, un moniteur et un sémaphore ?  - 2En d'autres termes, un seul thread à la fois peut fonctionner avec des ressources partagées. Les tentatives d'autres threads (personnes) pour accéder aux ressources occupées échoueront. Un mutex a plusieurs caractéristiques importantes. Tout d'abord , seuls deux états sont possibles : "déverrouillé" et "verrouillé". Cela nous aide à comprendre comment cela fonctionne : vous pouvez établir des parallèles avec des variables booléennes (vrai/faux) ou des nombres binaires (0/1). , l'état ne peut pas être contrôlé directement. Java n'a aucun mécanisme qui vous permettrait de prendre explicitement un objet, d'obtenir son mutex et d'attribuer le statut souhaité. En d'autres termes, vous ne pouvez pas faire quelque chose comme :

Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
Cela signifie que vous ne pouvez pas libérer le mutex d'un objet. Seule la machine Java y a directement accès. Les programmeurs travaillent avec des mutex à travers les outils du langage.

Moniteur

Un moniteur est une "superstructure" supplémentaire sur un mutex. En fait, un moniteur est un morceau de code "invisible" pour le programmeur. Lorsque nous avons parlé des mutex plus tôt, nous avons donné un exemple simple :

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       synchronized (obj) {

           // Logic available to just one thread at a time
       }
   }
}
Dans le bloc de code marqué du mot clé synchronized , le mutex de notre objet obj est acquis. Très bien, nous pouvons acquérir le cadenas, mais comment la "protection" est-elle exactement assurée? Quand on voit le mot synchronized , qu'est-ce qui empêche les autres threads d'entrer dans le bloc ? La protection vient d'un moniteur ! Le compilateur convertit le mot clé synchronized en plusieurs morceaux de code spéciaux. Encore une fois, revenons à notre exemple avec la méthode doSomething() . Nous y ajouterons :

public class Main {

   private Object obj = new Object();

   public void doSomething() {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time
       synchronized (obj) {

           /* Do important work that requires that the object
           be accessed by only one thread */
           obj.someImportantMethod();
       }
   }
}
Voici ce qui se passe "sous le capot" après que le compilateur a converti ce code :

public class Main {

   private Object obj = new Object();

   public void doSomething() throws InterruptedException {

       // ...some logic, available for all threads

       // Logic available to just one thread at a time:
     
       /* as long as the object's mutex is busy,
       all the other threads (except the one that acquired it) are put to sleep */
       while (obj.getMutex().isBusy()) {
           Thread.sleep(1);
       }

       // Mark the object's mutex as busy
       obj.getMutex().isBusy() = true;

       /* Do important work that requires that the object
       be accessed by only one thread */
       obj.someImportantMethod();

       // Free the object's mutex
       obj.getMutex().isBusy() = false;
   }
}
Bien sûr, ce n'est pas un exemple réel. Ici, nous avons utilisé un code de type Java pour décrire ce qui se passe à l'intérieur de la machine Java. Cela dit, ce pseudo-code donne une excellente compréhension de ce qui se passe réellement avec l'objet et les threads à l'intérieur du bloc synchronisé et comment le compilateur convertit ce mot clé en plusieurs instructions "invisibles" pour le programmeur. Fondamentalement, Java utilise le mot clé synchronized pour représenter un moniteur . Tout le code qui apparaît à la place du mot clé synchronized dans le dernier exemple est le moniteur.

Sémaphore

Un autre mot que vous rencontrerez dans votre étude personnelle du multithreading est "sémaphore". Voyons ce que c'est et en quoi cela diffère d'un moniteur et d'un mutex. Un sémaphore est un outil pour synchroniser l'accès à une ressource. Sa particularité est qu'il utilise un compteur pour créer le mécanisme de synchronisation. Le compteur nous indique combien de threads peuvent accéder simultanément à la ressource partagée. Quelle est la différence entre un mutex, un moniteur et un sémaphore ?  - 3Les sémaphores en Java sont représentés par la classe Semaphore . Lors de la création d'objets sémaphores, nous pouvons utiliser les constructeurs suivants :

Semaphore(int permits)
Semaphore(int permits, boolean fair)
Nous passons ce qui suit au constructeur :
    int permit — la valeur initiale et maximale du compteur. En d'autres termes, ce paramètre détermine combien de threads peuvent accéder simultanément à la ressource partagée ;
  • boolean fair — établit l'ordre dans lequel les threads auront accès. Si fair est true, alors l'accès est accordé aux threads en attente dans l'ordre dans lequel ils l'ont demandé. S'il est faux, alors l'ordre est déterminé par le planificateur de thread.
Un exemple classique d'utilisation des sémaphores est le problème du philosophe de la restauration. Quelle est la différence entre un mutex, un moniteur et un sémaphore ?  - 4Pour faciliter la compréhension, nous allons simplifier un peu. Imaginez que nous ayons 5 philosophes qui ont besoin de déjeuner. De plus, nous avons une table pouvant accueillir simultanément pas plus de deux personnes. Notre tâche est de nourrir tous les philosophes. Aucun d'entre eux ne doit avoir faim, et aucun d'entre eux ne doit se "bloquer" en essayant de s'asseoir à table (il faut éviter l'impasse). Voici à quoi ressemblera notre cours de philosophe :

class Philosopher extends Thread {

   private Semaphore sem;

   // Did the philosopher eat?
   private boolean full = false;

   private String name;

   Philosopher(Semaphore sem, String name) {
       this.sem=sem;
       this.name=name;
   }

   public void run()
   {
       try
       {
           // If the philosopher has not eaten
           if (!full) {
               // Ask the semaphore for permission to run
               sem.acquire();
               System.out.println(name + " takes a seat at the table");

               // The philosopher eats
               sleep(300);
               full = true;

               System.out.println(name + " has eaten! He leaves the table");
               sem.release();

               // The philosopher leaves, making room for others
               sleep(300);
           }
       }
       catch(InterruptedException e) {
           System.out.println("Something went wrong!");
       }
   }
}
Et voici le code pour exécuter notre programme :

public class Main {

   public static void main(String[] args) {

       Semaphore sem = new Semaphore(2);
       new Philosopher(sem, "Socrates").start();
       new Philosopher(sem,"Plato").start();
       new Philosopher(sem,"Aristotle").start();
       new Philosopher(sem, "Thales").start();
       new Philosopher(sem, "Pythagoras").start();
   }
}
Nous avons créé un sémaphore dont le compteur est mis à 2 pour satisfaire la condition : seuls deux philosophes peuvent manger en même temps. Autrement dit, seuls deux threads peuvent s'exécuter en même temps, car notre classe Philosopher hérite de Thread! Les méthodes learn() et release() de la classe Semaphore contrôlent son compteur d'accès. La méthode acquiert() demande au sémaphore l'accès à la ressource. Si le compteur est> 0, alors l'accès est accordé et le compteur est réduit de 1. Le release ()La méthode "libère" l'accès précédemment accordé, le renvoyant au compteur (augmente le compteur d'accès du sémaphore de 1). Qu'obtenons-nous lorsque nous exécutons le programme ? Le problème est-il résolu ? Nos philosophes ne se battront-ils pas en attendant leur tour ? :) Voici la sortie de la console que nous avons :

Socrates takes a seat at the table 
Plato takes a seat at the table 
Socrates has eaten! He leaves the table 
Plato has eaten! He leaves the table 
Aristotle takes a seat at the table 
Pythagoras takes a seat at the table 
Aristotle has eaten! He leaves the table 
Pythagoras has eaten! He leaves the table 
Thales takes a seat at the table 
Thales has eaten! He leaves the table 
Nous l'avons fait! Et bien que Thales ait dû dîner seul, je ne pense pas que nous l'ayons offensé :) Vous avez peut-être remarqué certaines similitudes entre un mutex et un sémaphore. En effet, ils ont la même mission : synchroniser l'accès à une ressource. Quelle est la différence entre un mutex, un moniteur et un sémaphore ?  - 5La seule différence est que le mutex d'un objet ne peut être acquis que par un seul thread à la fois, alors que dans le cas d'un sémaphore, qui utilise un compteur de threads, plusieurs threads peuvent accéder à la ressource simultanément. Ce n'est pas qu'une coïncidence :) Un mutex est en fait un sémaphore avec un compte de 1. En d'autres termes, c'est un sémaphore qui peut accueillir un seul thread. Il est également connu sous le nom de "sémaphore binaire" car son compteur ne peut avoir que 2 valeurs - 1 ("déverrouillé") et 0 ("verrouillé"). C'est ça! Comme vous pouvez le constater, ce n'est finalement pas si déroutant :) Maintenant , si vous souhaitez étudier le multithreading plus en détail sur Internet, il vous sera un peu plus facile de naviguer dans ces concepts. A bientôt dans les prochains cours !
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION