Usar un semáforo en C

Jinku Hu 12 octubre 2023
  1. Utilice semáforos POSIX para sincronizar el acceso a variables compartidas en C
  2. Utilice la función sem_destroy para destruir el semáforo sin nombre
Usar un semáforo en C

Este artículo demostrará varios métodos sobre cómo usar un semáforo en C.

Utilice semáforos POSIX para sincronizar el acceso a variables compartidas en C

Hay dos API de semáforos comunes en los sistemas basados ​​en UNIX: semáforos POSIX y semáforos System V. Se considera que este último tiene una interfaz menos simple y ofrece las mismas características que la API POSIX. Tenga en cuenta que los semáforos son otro mecanismo de sincronización como las exclusiones mutuas y se pueden utilizar en escenarios casi similares. Un semáforo es un número entero mantenido por el kernel, generalmente establecido en el valor inicial mayor o igual a 0.

Se pueden realizar dos operaciones en un objeto semáforo: incrementar o disminuir en uno, lo que corresponde a adquirir y liberar el recurso compartido. POSIX proporciona un tipo especial sem_t para un semáforo sin nombre, una herramienta más común en flujos de trabajo de subprocesos múltiples. La variable sem_t debe inicializarse con la función sem_init que también indica si el semáforo dado debe compartirse entre procesos o subprocesos de un proceso. Una vez inicializada la variable, podemos implementar la sincronización mediante las funciones sem_post y sem_wait. sem_post incrementa el semáforo, que normalmente corresponde al desbloqueo del recurso compartido. Por el contrario, sem_wait disminuye el semáforo y denota el bloqueo del recurso. Por lo tanto, la sección crítica debería comenzar con sem_wait y terminar con la llamada sem_post. Sin embargo, tenga en cuenta que la verificación del código de estado de éxito puede ser esencial para depurar el código.

#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 de muestra:

./program_name 1000

Producción :

shared = 4000

Utilice la función sem_destroy para destruir el semáforo sin nombre

Un semáforo inicializado con una llamada sem_init debe destruirse utilizando la función sem_destroy. Sin embargo, tenga en cuenta que se debe llamar a sem_destroy cuando ninguno de los procesos / subprocesos lo esté esperando. Omitir la llamada sem_destroy puede resultar en una pérdida de memoria en algunos sistemas.

Generalmente, los semáforos tienen un rendimiento similar en comparación con los mutex de Pthread, pero este último suele ser el preferido para una mejor estructura de código. Sin embargo, hay algunos escenarios en los que el bloqueo debe modificarse desde el manejador de señales, lo que requiere que la función sea asincrónica segura, y solo se implementa sem_post como tal. También hay un semáforo con nombre en la API POSIX, que puede persistir incluso después de que finalice un hilo que lo creó y lo usó.

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