CodeGym /Blog Java /Random-ES /Diferencia entre un mutex, un monitor y un semáforo
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Diferencia entre un mutex, un monitor y un semáforo

Publicado en el grupo Random-ES
¡Hola! Cuando estudiaba subprocesos múltiples en CodeGym, frecuentemente se encontraba con los conceptos de "mutex" y "monitor". Sin mirar, ¿puedes decir en qué se diferencian? :) Si es así, ¡bien hecho! Si no (esto es lo más común), no es una sorpresa. "Mutex" y "monitor" son en realidad conceptos relacionados. Además, cuando lea lecciones y vea videos sobre subprocesos múltiples en otros sitios web, encontrará otro concepto similar: "semáforo". También tiene una función muy similar a los monitores y mutexes. Por eso vamos a investigar estos tres términos. Veremos algunos ejemplos y llegaremos a una comprensión definitiva de cómo estos conceptos difieren entre sí :)

exclusión mutua

Un mutex (o candado) es un mecanismo especial para sincronizar subprocesos. Uno está "adjunto" a cada objeto en Java; ya lo sabe :) No importa si usa clases estándar o crea sus propias clases, por ejemplo, Cat and Dog : todos los objetos de todas las clases tienen un mutex . El término "mutex" proviene de "MUTual EXclusion", que describe perfectamente su propósito. Como dijimos en una de nuestras lecciones anteriores, un mutex permite garantizar que solo un subproceso a la vez tenga acceso al objeto. Un ejemplo popular de la vida real de un mutex involucra inodoros. Cuando una persona ingresa a una partición de baño, cierra la puerta desde adentro. El inodoro es como un objeto al que se puede acceder por múltiples hilos. La cerradura de la puerta divisoria es como un mutex, y la fila de personas afuera representa hilos. La cerradura de la puerta es el mutex del inodoro: asegura que solo una persona pueda entrar. ¿Cuál es la diferencia entre un mutex, un monitor y un semáforo?  - 2En otras palabras, solo un subproceso a la vez puede funcionar con recursos compartidos. Los intentos de otros subprocesos (personas) de obtener acceso a los recursos ocupados fallarán. Un mutex tiene varias características importantes. Primero , solo son posibles dos estados: "desbloqueado" y "bloqueado". Esto nos ayuda a entender cómo funciona: puedes dibujar paralelos con variables booleanas (verdadero/falso) o números binarios (0/1). , el estado no puede ser controlado directamente. Java no tiene ningún mecanismo que le permita tomar explícitamente un objeto, obtener su exclusión mutua y asignar el estado deseado. En otras palabras, no puedes hacer algo como:

Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
Esto significa que no puede liberar la exclusión mutua de un objeto. Solo la máquina Java tiene acceso directo a él. Los programadores trabajan con mutexes a través de las herramientas del lenguaje.

Monitor

Un monitor es una "superestructura" adicional sobre un mutex. De hecho, un monitor es un trozo de código que es "invisible" para el programador. Cuando hablamos de mutexes anteriormente, dimos un ejemplo 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
       }
   }
}
En el bloque de código marcado con la palabra clave sincronizada , se adquiere el mutex de nuestro objeto obj . Genial, podemos adquirir el candado, pero ¿cómo se proporciona exactamente la "protección"? Cuando vemos la palabra sincronizado , ¿qué impide que los demás subprocesos entren en el bloque? ¡La protección viene de un monitor! El compilador convierte la palabra clave sincronizada en varias piezas especiales de código. Una vez más, volvamos a nuestro ejemplo con el método doSomething() . Le agregaremos:

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();
       }
   }
}
Esto es lo que sucede "bajo el capó" después de que el compilador convierte este código:

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;
   }
}
Por supuesto, este no es un ejemplo real. Aquí, usamos código similar a Java para representar lo que sucede dentro de la máquina Java. Dicho esto, este pseudocódigo brinda una excelente comprensión de lo que realmente sucede con el objeto y los subprocesos dentro del bloque sincronizado y cómo el compilador convierte esta palabra clave en varias declaraciones que son "invisibles" para el programador. Básicamente, Java usa la palabra clave sincronizada para representar un monitor . Todo el código que aparece en lugar de la palabra clave sincronizada en el último ejemplo es el monitor.

Semáforo

Otra palabra que encontrará en su estudio personal de subprocesos múltiples es "semáforo". Averigüemos qué es esto y en qué se diferencia de un monitor y un mutex. Un semáforo es una herramienta para sincronizar el acceso a algún recurso. Su característica distintiva es que utiliza un contador para crear el mecanismo de sincronización. El contador nos dice cuántos hilos pueden acceder simultáneamente al recurso compartido. ¿Cuál es la diferencia entre un mutex, un monitor y un semáforo?  - 3Los semáforos en Java están representados por la clase Semaphore . Al crear objetos de semáforo, podemos usar los siguientes constructores:

Semaphore(int permits)
Semaphore(int permits, boolean fair)
Pasamos lo siguiente al constructor:
    int allow — el valor inicial y máximo del contador. En otras palabras, este parámetro determina cuántos subprocesos pueden acceder simultáneamente al recurso compartido;
  • boolean fair : establece el orden en que los subprocesos obtendrán acceso. Si justo es verdadero, se otorga acceso a los subprocesos en espera en el orden en que lo solicitaron. Si es falso, el programador de subprocesos determina el orden.
Un ejemplo clásico del uso de semáforos es el problema del filósofo comedor. ¿Cuál es la diferencia entre un mutex, un monitor y un semáforo?  - 4Para facilitar la comprensión, lo simplificaremos un poco. Imagina que tenemos 5 filósofos que necesitan almorzar. Además, tenemos una mesa que puede acomodar simultáneamente a no más de dos personas. Nuestra tarea es alimentar a todos los filósofos. Ninguno de ellos debe pasar hambre, y ninguno de ellos debe "bloquearse" al tratar de sentarse a la mesa (debemos evitar el punto muerto). Así es como se verá nuestra clase de filósofo:

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!");
       }
   }
}
Y aquí está el código para ejecutar nuestro programa:

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();
   }
}
Creamos un semáforo cuyo contador se establece en 2 para satisfacer la condición: solo dos filósofos pueden comer al mismo tiempo. Es decir, solo se pueden ejecutar dos subprocesos al mismo tiempo, porque nuestra clase Philosopher hereda Thread . Los métodos de adquisición () y liberación () de la clase Semaphore controlan su contador de acceso. El método de adquisición () le pide al semáforo acceso al recurso. Si el contador es >0, entonces se otorga acceso y el contador se reduce en 1. El release()El método "libera" el acceso previamente otorgado, devolviéndolo al contador (aumenta el contador de acceso del semáforo en 1). ¿Qué obtenemos cuando ejecutamos el programa? ¿Está resuelto el problema? ¿No lucharán nuestros filósofos mientras esperan su turno? :) Aquí está la salida de la consola que obtuvimos:

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 
¡Lo hicimos! Y aunque Thales tuvo que cenar solo, no creo que lo hayamos ofendido :) Es posible que haya notado algunas similitudes entre un mutex y un semáforo. Efectivamente, tienen la misma misión: sincronizar el acceso a algún recurso. ¿Cuál es la diferencia entre un mutex, un monitor y un semáforo?  - 5La única diferencia es que el mutex de un objeto solo puede ser adquirido por un subproceso a la vez, mientras que en el caso de un semáforo, que utiliza un contador de subprocesos, varios subprocesos pueden acceder al recurso simultáneamente. Esto no es solo una coincidencia :) Un mutex es en realidad un semáforocon una cuenta de 1. En otras palabras, es un semáforo que puede acomodar un solo hilo. También se conoce como "semáforo binario" porque su contador solo puede tener 2 valores: 1 ("desbloqueado") y 0 ("bloqueado"). ¡Eso es todo! Como puede ver, no es tan confuso después de todo :) Ahora, si desea estudiar los subprocesos múltiples con más detalle en Internet, le resultará un poco más fácil navegar por estos conceptos. ¡Nos vemos en las próximas lecciones!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION