Utiliser un sémaphore en C

Jinku Hu 12 octobre 2023
  1. Utiliser les sémaphores POSIX pour synchroniser l’accès aux variables partagées en C
  2. Utilisez la fonction sem_destroy pour détruire le sémaphore sans nom
Utiliser un sémaphore en C

Cet article présente plusieurs méthodes d’utilisation d’un sémaphore en C.

Utiliser les sémaphores POSIX pour synchroniser l’accès aux variables partagées en C

Il existe deux API de sémaphore courantes sur les systèmes UNIX: les sémaphores POSIX et les sémaphores System V. Cette dernière est considérée comme ayant une interface moins simple tout en offrant les mêmes fonctionnalités que l’API POSIX. Notez que les sémaphores sont encore un autre mécanisme de synchronisation comme les mutex et peuvent être utilisés dans la plupart des scénarios similaires. Un sémaphore est un entier maintenu par le noyau, généralement fixé à la valeur initiale supérieure ou égale à 0.

Deux opérations peuvent être effectuées sur un objet sémaphore - incrémenter ou décrémenter de un, ce qui correspond à l’acquisition et à la libération de la ressource partagée. POSIX fournit un type spécial sem_t pour un sémaphore sans nom, un outil plus courant dans les workflows multi-threads. La variable sem_t doit être initialisée avec la fonction sem_init qui indique également si le sémaphore donné doit être partagé entre les processus ou les threads d’un processus. Une fois la variable initialisée, nous pouvons implémenter la synchronisation à l’aide des fonctions sem_post et sem_wait. sem_post incrémente le sémaphore, ce qui correspond généralement au déverrouillage de la ressource partagée. En revanche, sem_wait décrémente le sémaphore et désigne le verrouillage de la ressource. Ainsi, la section critique devrait commencer par sem_wait et se terminer par l’appel sem_post. Notez cependant que la vérification du code d’état de réussite peut être essentielle pour déboguer le code.

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>

static long shared = 0;
static sem_t sem;

enum { THREADS = 4 };

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

static void *threadFunc(void *arg) {
  long loops = *((long *)arg);

  for (long j = 0; j < loops; j++) {
    if (sem_wait(&sem) == -1) errExit("sem_wait");

    shared++;

    if (sem_post(&sem) == -1) errExit("sem_post");
  }

  return NULL;
}

int main(int argc, char *argv[]) {
  pthread_t t[THREADS];
  int s;
  long nloops;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s num_loops\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  nloops = strtol(argv[1], NULL, 0);

  if (sem_init(&sem, 0, 1) == -1) errExit("sem_init");

  for (int i = 0; i < THREADS; ++i) {
    s = pthread_create(&t[i], NULL, threadFunc, &nloops);
    if (s != 0) errExit("pthread_create");
  }

  for (int i = 0; i < THREADS; ++i) {
    s = pthread_join(t[i], NULL);
    if (s != 0) errExit("pthread_join");
  }

  printf("shared = %ld\n", shared);
  exit(EXIT_SUCCESS);
}

Exemple de commande:

./program_name 1000

Production:

shared = 4000

Utilisez la fonction sem_destroy pour détruire le sémaphore sans nom

Un sémaphore initialisé avec un appel sem_init doit être détruit à l’aide de la fonction sem_destroy. Notez cependant que sem_destroy doit être appelé quand aucun des processus / threads ne l’attend. L’omission de l’appel sem_destroy peut entraîner une fuite de mémoire sur certains systèmes.

En général, les sémaphores ont des performances similaires à celles des mutex Pthread, mais ce dernier est généralement préféré pour une meilleure structure de code. Cependant, il existe certains scénarios où le verrou devrait être modifié à partir du gestionnaire de signaux, ce qui nécessite que la fonction soit async-safe, et seul sem_post est implémenté en tant que tel. Il existe également un sémaphore nommé dans l’API POSIX, qui peut persister même après la fin d’un thread qui l’a créé et utilisé.

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <stdlib.h>

static long shared = 0;
static sem_t sem;

enum { THREADS = 4 };

#define errExit(msg)    \
  do {                  \
    perror(msg);        \
    exit(EXIT_FAILURE); \
  } while (0)

static void *threadFunc(void *arg) {
  long loops = *((long *)arg);

  for (long j = 0; j < loops; j++) {
    if (sem_wait(&sem) == -1) errExit("sem_wait");

    shared++;

    if (sem_post(&sem) == -1) errExit("sem_post");
  }

  return NULL;
}

int main(int argc, char *argv[]) {
  pthread_t t[THREADS];
  int s;
  long nloops;

  if (argc != 2) {
    fprintf(stderr, "Usage: %s num_loops\n", argv[0]);
    exit(EXIT_FAILURE);
  }

  nloops = strtol(argv[1], NULL, 0);

  if (sem_init(&sem, 0, 1) == -1) errExit("sem_init");

  for (int i = 0; i < THREADS; ++i) {
    s = pthread_create(&t[i], NULL, threadFunc, &nloops);
    if (s != 0) errExit("pthread_create");
  }

  for (int i = 0; i < THREADS; ++i) {
    s = pthread_join(t[i], NULL);
    if (s != 0) errExit("pthread_join");
  }

  printf("shared = %ld\n", shared);

  sem_destroy(&sem);
  exit(EXIT_SUCCESS);
}
Auteur: Jinku Hu
Jinku Hu avatar Jinku Hu avatar

Founder of DelftStack.com. Jinku has worked in the robotics and automotive industries for over 8 years. He sharpened his coding skills when he needed to do the automatic testing, data collection from remote servers and report creation from the endurance test. He is from an electrical/electronics engineering background but has expanded his interest to embedded electronics, embedded programming and front-/back-end programming.

LinkedIn Facebook