Mulithreading C++20 : lecteurs-rédateur

On propose une solution au problème des lecteurs-rédacteur en C++.

Description

Là encore il s’agit d’un problème de partage de ressource entre 2 processus, mais cette fois-ci de manière non symétrique, puisque les processus auront des priorités différentes.

Rôle des rédacteurs

Les rédacteurs doivent accéder à la ressource de manière exclusive et donc attendre si au moins un lecteur la possède.

Rôles des lecteurs

Les lecteurs peuvent :

  • accéder à la ressource si aucun rédacteur ne la possède;
  • se partager la ressource tant qu’au moins l’un d’entre eux y a accés (au détriment des rédacteurs).

Hypothèses

On suppose que :

  1. la zone partagée est de taille fixe;
  2. les vitesses des processus sont quelconques.

Analyse

Communication inter-processus sur la disponibilité de données

La communication entre les processus va être similaire au cas précédent. Les processus des différentes catégories doivent pouvoir se signaler entre eux de la possilité ou non d’accéder à la ressource.

Une fois que la ressource en cours de lecture, le rédacteur ne pourra y accéder que lorsque tous les lecteurs souhaitant y accéder auront terminé. À l’inverse, quand un rédacteur posséde la ressource, aucun lecteur n’y accédera. La notion de priorité sera gérée par le premier lecteur accédant (qui bloquera le rédacteur), ainsi que par le dernier (qui libérera la ressource).

Ce mécanisme est réalisé à l’aide d’un sémaphore qui sera pris par le premier lecteur et relâché par le dernier.

Section critique

Dans ce mécanisme, il faut assurer la cohérence de la valeur du nombre de lecteurs qui sera modifié par chaque lecteur au moment où il accéde à la ressource, et au moment où il la libère. Pour verrouiller sa modificiation on utilisera un mutex.

Code

Les données partagées

La ressource partagée est ici representée par un entier. La variable n_readers contient le nombre de lecteurs en train d’accéder à la ressource.

// This represents the ressource shared between process, irl it would be a file or a device
int shared_ressource = 0;

// Number of readers reading
unsigned int n_readers = 0;

// Mutex to protect the number of readers variable
std::mutex readers_mutex;

// Ressource is free at start
std::binary_semaphore sem_write{1};

Le rédacteur

Le rédacteur ne peut pas agir tant que le sémaphore indiquant la possibilté d’écrire ne peut peut être acquis. Une fois acquis, il modifie la valeur partagée.

void writer(void* args) {
  while(true) {
    sem_write.acquire();
    shared_ressource++;
    sem_write.release();
  }
}

Les lecteurs

Le premier lecteur prend l’accés à la ressource, et le dernier la libère.

void reader(void* args) {
  while(true) {
    {
      // The first reader takes the semaphore
      std::lock_guard<std::mutex> lock(readers_mutex);
      if (++n_readers) sem_write.acquire();
    }


    // Reads...
    int value = shared_ressource;
    (void) value;

    {
      // The last one releases the semaphore 
      std::lock_guard<std::mutex> lock(readers_mutex);
      if (--n_readers == 0) sem_write.release();
    }
  }
}

Résultat

Le code est disponible sur le dépôt Github