C semaphore

De The Linux Craftsman
Aller à la navigation Aller à la recherche

Introduction

Les sémaphores permettent de gérer ce que l'on appelle les sections critiques. Il y a des parties de code où l'on souhaite que toutes les instructions soient exécutées en évitant les interblocages un peu comme avec un mutex.

On peut se demander quelle est la différence avec un mutex ?

Un mutex est un mécanisme de verrouillage utilisé pour synchroniser l'accès à une ressource alors qu'un sémaphore est un mécanisme de signalisation utilisé pour sérialiser l'exécution dans une portion de code. Un peu comme si les processus se passaient le mot: "J'ai fini, tu peux y aller".

Utilisation

Création

Tout d'abord, il faut créer un objet de type sémaphore:

#include <semaphore.h>

sem_t semaphore;

Initialisation

Une fois l'objet créé, on peut l'initialiser:

#include <semaphore.h>

int sem_init(sem_t *semaphore, int pshared, unsigned int valeur);
  • semaphore → pointeur vers le sémaphore à initialiser ;
  • pshared → drapeau qui précise si le sémaphore est utilisé par des threads (valeur 0) ;
  • valeur → valeur de départ du sémaphore.
  • le code retour varie entre:
    • '0' si tout s'est bien passé
    • '1' si une erreur survient et errno est positionné

Blocage

Une fois le sémaphore initialisé, on peut demander son blocage avec

#include <semaphore.h>

int sem_wait(sem_t *semaphore);
  • semaphore → pointeur vers le sémaphore à bloquer ;
  • le code retour varie entre:
    • '0' si tout s'est bien passé
    • '-1' si une erreur survient et errno est positionné

Warning-icon.png

Cette fonction est bloquante !
#include <semaphore.h>

int sem_trywait(sem_t *semaphore);
  • semaphore → pointeur vers le sémaphore à bloquer ;
  • le code retour varie entre:
    • '0' si tout s'est bien passé
    • '-1' si une erreur survient et errno est positionné

Warning-icon.png

Cette fonction est non bloquante !
#include <semaphore.h>
#include <time.h>

int sem_timedwait(sem_t *semaphore, const struct timespec *abs_timeout);
  • semaphore → pointeur vers le sémaphore à bloquer ;
  • le code retour varie entre:
    • '0' si tout s'est bien passé
    • '-1' si une erreur survient et errno est positionné

Warning-icon.png

Cette fonction est bloquante le temps spécifié !

Voici le détail de la structure timespec:

struct timespec {
   time_t tv_sec;      /* Seconds */
   long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
};

Relâchement d'un sémaphore

Une fois la section critique passée, on peut relâcher le sémaphore:

#include <semaphore.h>

int sem_post(sem_t *semaphore);
  • semaphore → pointeur vers le sémaphore à bloquer ;
  • le code retour varie entre:
    • '0' si tout s'est bien passé
    • '-1' si une erreur survient et errno est positionné

Exemple

Ci-dessous un exemple d'utilisation d'un sémaphore:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <pthread.h>
#include <errno.h>
#include <semaphore.h>

#define NB_THREAD 4
#define LIMIT 2

// Création du sémaphore;
sem_t semaphore;

void * job(void * args) {
	// Récupération de l'identifiant du thread
	int tid = pthread_self();
	int i = 0;
	while (i < LIMIT) {
		// On attend la disponibilité du sémaphore
		sem_wait(&semaphore);
		// Section critique
		printf("Je suis le thread [%i] et je vais dormir 1 seconde\n", tid);
		sleep(1);
		printf("Je suis le thread [%i] et j'ai fini ma sieste\n", tid);
		// On relache le sémaphore
		sem_post(&semaphore);
		i++;
	}
	pthread_exit(EXIT_SUCCESS);
}

int main() {
	// Création d'un tableau de thread
	pthread_t threads[NB_THREAD];
	// Initialisation du sémaphore
	sem_init(&semaphore, PTHREAD_PROCESS_SHARED, 1);
	for (int i = 0; i < NB_THREAD; i++) {
		int err;
		if ((err = pthread_create(&threads[i], NULL, job, NULL)) != 0) {
			printf("Echec de la création du thread: [%s]", strerror(err));
			return EXIT_FAILURE;;
		}
		printf("Création du thread numéro %i\n", i);
	}
	for (int i = 0; i < NB_THREAD; i++) {
		pthread_join(threads[i], NULL);
	}
	sem_destroy(&semaphore);
	return EXIT_SUCCESS;
}

Ce programme donne, par exemple, la sortie suivante:

Création du thread numéro 0
Création du thread numéro 1
Création du thread numéro 2
Création du thread numéro 3
Je suis le thread [-1171347712] et je vais dormir 1 seconde
Je suis le thread [-1171347712] et j'ai fini ma sieste
Je suis le thread [-1171347712] et je vais dormir 1 seconde
Je suis le thread [-1171347712] et j'ai fini ma sieste
Je suis le thread [-1192327424] et je vais dormir 1 seconde
Je suis le thread [-1192327424] et j'ai fini ma sieste
Je suis le thread [-1192327424] et je vais dormir 1 seconde
Je suis le thread [-1192327424] et j'ai fini ma sieste
Je suis le thread [-1181837568] et je vais dormir 1 seconde
Je suis le thread [-1181837568] et j'ai fini ma sieste
Je suis le thread [-1181837568] et je vais dormir 1 seconde
Je suis le thread [-1181837568] et j'ai fini ma sieste
Je suis le thread [-1160857856] et je vais dormir 1 seconde
Je suis le thread [-1160857856] et j'ai fini ma sieste
Je suis le thread [-1160857856] et je vais dormir 1 seconde
Je suis le thread [-1160857856] et j'ai fini ma sieste

On voit bien les threads exécuter la section critique les uns après les autres !