Sémaphores et mutex

Les sémaphores et les mutex sont des contrôles de concurrence utilisés pour synchroniser l’accès de plusieurs threads aux ressources partagées.

Sémaphore

Voici une brillante explication de cette question Stackoverflow:

Considérez les sémaphores comme des videurs dans une boîte de nuit. Il y a un dédié nombre de personnes autorisées dans le club à la fois. Si le club est plein personne n’est autorisé à entrer, mais dès qu’une personne sort une autre personne pourrait entrer.

C’est simplement un moyen de limiter le nombre de consommateurs d’une ressource spécifique. Par exemple, pour limiter le nombre de appels simultanés à une base de données dans une application.

Mutex

Un mutex est un sémaphore de 1 (c’est-à-dire un seul thread à la fois). En utilisant la métaphore de la boîte de nuit, pensez à un mutex en termes de cabine de toilette dans la boîte de nuit. Un seul occupant autorisé à la fois.

Mutex en Java et C++

Bien que Java n’ait pas de classe Mutex, vous pouvez imiter un Mutex en utilisant un sémaphore de 1. L’exemple suivant exécute deux threads avec et sans verrouillage. Sans verrouillage, le programme crache un ordre quelque peu aléatoire de caractères de sortie ($ ou #). Avec le verrouillage, le programme crache de jolis jeux de caractères ordonnés de ##### ou $$$$$, mais jamais un mélange de # et $.

import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadLocalRandom;

public class MutexTest {
   static Semaphore semaphore = new Semaphore(1);

   static class MyThread extends Thread {
      boolean lock;
      char c = ' ';

      MyThread(boolean lock, char c) {
         this.lock = lock;
         this.c = c;
      }

      public void run() {
         try {
            // Generate a random number between 0 & 50
            // The random nbr is used to simulate the "unplanned"
            // execution of the concurrent code
            int randomNbr = ThreadLocalRandom.current().nextInt(0, 50 + 1);

            for (int j=0; j<10; ++j) {
               if(lock) semaphore.acquire();
               try {
                  for (int i=0; i<5; ++i) {
                     System.out.print(c);
                     Thread.sleep(randomNbr);
                  }
               } finally {
                  if(lock) semaphore.release();
               }
               System.out.print('|');
            }
         } catch (InterruptedException e) {
            e.printStackTrace();
         }
      }
   }

   public static void main(String[] args) throws Exception {
      System.out.println("Without Locking:");
      MyThread th1 = new MyThread(false, '$');
      th1.start();
      MyThread th2 = new MyThread(false, '#');
      th2.start();
      
      th1.join();
      th2.join();

      System.out.println('\n');

      System.out.println("With Locking:");
      MyThread th3 = new MyThread(true, '$');
      th3.start();
      MyThread th4 = new MyThread(true, '#');
      th4.start();
      
      th3.join();
      th4.join();

      System.out.println('\n');
   }
}

Exécutez javac MutexTest.java ; java MutexTest, et vous obtiendrez quelque chose comme ceci :

Sans Verrouillage : #$$$$$|$$$$$|$$#$$$|$$$$$|$$$$#$|$$$$$|$$$$$|$#$$ $$|$$$$$|$$$#$$||#####|#####|#####|#####|#####|### ##|#####|#####|#####|

Avec Verrouillage : $$$$$|#####|$$$$$|#####|$$$$$|#####|$$$$$|#####|$ $$$$|#####|$$$$$|#####|$$$$$|#####|$$$$$|#####|$$$ $$|#####|$$$$$|#####|

Voici le même exemple en C++ :

#include <iostream>       // std::cout
#include <thread>         // std::thread
#include <mutex>          // std::mutex
#include <random>         // std::random_device

class MutextTest {
   private:
      static std::mutex mtx;  // mutex for critical section

   public:
      static void run(bool lock, char c) {
         // Generate a random number between 0 & 50
         // The random nbr is used to simulate the "unplanned"
         // execution of the concurrent code
         std::uniform_int_distribution<int> dist(0, 50);
         std::random_device rd;
         int randomNbr = dist(rd);
         //std::cout << randomNbr << '\n';

        for(int j=0; j<10; ++j) {
           if(lock) mtx.lock();
           for (int i=0; i<5; ++i) {
              std::cout << c << std::flush;
              std::this_thread::sleep_for(std::chrono::milliseconds(randomNbr));
           }
           std::cout << '|';
           if(lock) mtx.unlock();
        }
      }
};

std::mutex MutextTest::mtx;

int main()
{
  std::cout << "Without Locking:\n";
  std::thread th1 (MutextTest::run, false, '$');
  std::thread th2 (MutextTest::run, false, '#');

  th1.join();
  th2.join();

  std::cout << "\n\n";

  std::cout << "With Locking:\n";
  std::thread th3 (MutextTest::run, true, '$');
  std::thread th4 (MutextTest::run, true, '#');

  th3.join();
  th4.join();

  std::cout << '\n';

  return 0;
}

Exécutez g++ –std=c++11 MutexTest.cpp ; ./a.out, et vous obtiendrez quelque chose comme ceci :

Sans Verrouillage : $#$#$#$#$#|$|#$#$#$#$#|$$|#$#$#$#|$#$|#$#$#$#|$$# $|#$#$#|$#$#$|#$#$#|$#$$#$|#$#|$#$#$#$|#$#|$#$#$#$ $|#|$#$#$#$#$|#|####|

Avec Verrouillage : $$$$$|#####|$$$$$|#####|$$$$$|#####|$$$$$|#####|$ $$$$|#####|$$$$$|#####|$$$$$|#####|$$$$$|#####|$$$ $$|#####|$$$$$|#####|