Usa un semaforo in C

Jinku Hu 12 ottobre 2023
  1. Usa i semafori POSIX per sincronizzare l’accesso alle variabili condivise in C
  2. Usa la funzione sem_destroy per distruggere il semaforo senza nome
Usa un semaforo in C

Questo articolo illustrerà più metodi su come utilizzare un semaforo in C.

Usa i semafori POSIX per sincronizzare l’accesso alle variabili condivise in C

Esistono due API semaforo comuni sui sistemi basati su UNIX: semafori POSIX e semafori System V. Quest’ultimo è considerato avere un’interfaccia meno semplice pur offrendo le stesse funzionalità dell’API POSIX. Si noti che i semafori sono ancora un altro meccanismo di sincronizzazione come i mutex e possono essere utilizzati in scenari per lo più simili. Un semaforo è un numero intero mantenuto dal kernel, solitamente impostato al valore iniziale maggiore o uguale a 0.

È possibile eseguire due operazioni su un oggetto semaforo: incremento o decremento di uno, che corrisponde all’acquisizione e al rilascio della risorsa condivisa. POSIX fornisce uno speciale tipo sem_t per un semaforo senza nome, uno strumento più comune nei flussi di lavoro multi-thread. La variabile sem_t deve essere inizializzata con la funzione sem_init che indica anche se il semaforo dato deve essere condiviso tra processi o thread di un processo. Una volta inizializzata la variabile, possiamo implementare la sincronizzazione utilizzando le funzioni sem_post e sem_wait. sem_post incrementa il semaforo, che di solito corrisponde allo sblocco della risorsa condivisa. Al contrario, sem_wait decrementa il semaforo e denota il blocco della risorsa. Quindi, la sezione critica dovrebbe iniziare con sem_wait e finire con la chiamata sem_post. Tieni presente, tuttavia, che il controllo del codice di stato di successo può essere essenziale per eseguire il debug del codice.

#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);
}

Comando di esempio:

./program_name 1000

Produzione:

shared = 4000

Usa la funzione sem_destroy per distruggere il semaforo senza nome

Un semaforo inizializzato con una chiamata sem_init deve essere distrutto usando la funzione sem_destroy. Nota però che sem_destroy dovrebbe essere chiamato quando nessuno dei processi / thread lo sta aspettando. Omettere la chiamata sem_destroy può provocare una perdita di memoria su alcuni sistemi.

Generalmente, i semafori hanno prestazioni simili rispetto ai mutex Pthread, ma quest’ultimo è solitamente preferito per una migliore struttura del codice. Tuttavia, ci sono alcuni scenari in cui il lock dovrebbe essere modificato dal gestore del segnale, che richiede che la funzione sia asincrona e solo sem_post è implementato come tale. C’è anche un semaforo denominato nell’API POSIX, che può persistere anche dopo che un thread che lo ha creato e utilizzato, termina.

#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);
}
Autore: 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