Aller au contenu

Semaphore¤

Le semaphore a été introduit par Edsger Dijkstra en 1965. Il s'agit d'une variable entière non négative qui peut être utilisée pour synchroniser l'accès à une ressource partagée. Un sémaphore est un objet de synchronisation qui permet à plusieurs threads d'accéder à une ressource partagée en même temps.

Historiquement, puisque Dijkstra était Néerlandais, il a choisi comme noms de signaux :

  • P pour « Proberen » (essayer en néerlandais)
  • V pour « Verhogen » (augmenter en néerlandais)

Un sémaphore était vu comme la brique de base pour la synchronisation de threads. Il est toujours utilisé dans les systèmes d'exploitation modernes pour implémenter des mécanismes de synchronisation tels que les mutex, les moniteurs, les barrières, etc.

Néanmoins, on peut émuler un sémaphore avec un mutex et une variable condition. C'est ce que fait la classe std::counting_semaphore de la bibliothèque standard C++20. Voici comment on peut émuler un sémaphore avec un mutex et une variable condition :

#include <mutex>
#include <condition_variable>

class Semaphore {
    std::mutex mtx;
    std::condition_variable cv;
    int count;

public:
    Semaphore(int count = 0) : count(count) {}

    // Proberen
    void notify() {
        std::unique_lock<std::mutex> lock(mtx);
        ++count;
        cv.notify_one();
    }

    // Verhogen
    void wait() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return count > 0; });
        --count;
    }
};

Dans cet exemple, la méthode notify incrémente le compteur du sémaphore et notifie un thread en attente. La méthode wait attend que le compteur soit supérieur à zéro, puis le décrémente.

Il est parfois utile pour compter des ressources de fournir aux méthodes notify et wait un argument n pour incrémenter ou décrémenter le compteur de n unités. Cela permet de libérer ou d'acquérir plusieurs ressources en une seule opération. Par exemple, si n est égal à 3, cela signifie que trois ressources sont libérées ou acquises en une seule opération.

#include <mutex>
#include <condition_variable>

class Semaphore {
    std::mutex mtx;
    std::condition_variable cv;
    int count;

public:
    Semaphore(int count = 0) : count(count) {}

    void notify(int n = 1) {
        std::unique_lock<std::mutex> lock(mtx);
        count += n;
        cv.notify_all();
    }

    void wait(int n = 1) {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this, n] { return count >= n; });
        count -= n;
    }
};

Problème du producteur et du consommateur¤

Le problème du producteur et du consommateur est un problème classique de synchronisation de threads. Il s'agit de synchroniser un ou plusieurs threads producteurs qui produisent des données et un ou plusieurs threads consommateurs qui consomment ces données.

Le problème du producteur et du consommateur peut être résolu à l'aide de deux sémaphores :

  • Un sémaphore empty pour compter le nombre d'emplacements vides dans le tampon
  • Un sémaphore full pour compter le nombre d'emplacements pleins dans le tampon