Use um semáforo em C

Jinku Hu 12 outubro 2023
  1. Use semáforos POSIX para sincronizar o acesso a variáveis ​​compartilhadas em C
  2. Use a função sem_destroy para destruir o semáforo sem nome
Use um semáforo em C

Este artigo demonstrará vários métodos sobre como usar um semáforo em C.

Use semáforos POSIX para sincronizar o acesso a variáveis ​​compartilhadas em C

Existem duas APIs de semáforo comuns em sistemas baseados em UNIX - semáforos POSIX e semáforos System V. Este último é considerado como tendo uma interface menos simples, mas oferecendo os mesmos recursos da API POSIX. Observe que os semáforos são outro mecanismo de sincronização, como mutexes, e podem ser utilizados em cenários semelhantes. Um semáforo é um inteiro mantido pelo kernel, geralmente definido com o valor inicial maior ou igual a 0.

Duas operações podem ser feitas em um objeto semáforo - incremento ou decremento em um, que corresponde à aquisição e liberação do recurso compartilhado. POSIX fornece um tipo especial sem_t para um semáforo sem nome, uma ferramenta mais comum em fluxos de trabalho multithread. A variável sem_t deve ser inicializada com a função sem_init que também indica se o semáforo dado deve ser compartilhado entre processos ou threads de um processo. Uma vez que a variável é inicializada, podemos implementar a sincronização usando as funções sem_post e sem_wait. sem_post incrementa o semáforo, que geralmente corresponde ao desbloqueio do recurso compartilhado. Em contraste, sem_wait diminui o semáforo e denota o bloqueio do recurso. Assim, a seção crítica precisaria começar com sem_wait e terminar com chamada sem_post. Lembre-se, porém, de que a verificação do código de status de sucesso pode ser essencial para depurar o 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 amostra:

./program_name 1000

Resultado:

shared = 4000

Use a função sem_destroy para destruir o semáforo sem nome

Um semáforo inicializado com uma chamada sem_init deve ser destruído usando a função sem_destroy. Observe, entretanto, que sem_destroy deve ser chamado quando nenhum dos processos / threads estiver esperando por ele. A omissão da chamada sem_destroy pode resultar em um vazamento de memória em alguns sistemas.

Geralmente, os semáforos têm um desempenho semelhante em comparação com os mutexes Pthread, mas o último geralmente é preferido para uma melhor estrutura de código. No entanto, existem alguns cenários onde o bloqueio deve ser modificado a partir do manipulador de sinal, o que requer que a função seja assíncrona segura, e apenas sem_post é implementado como tal. Também existe um semáforo nomeado na API POSIX, que pode persistir mesmo após o encerramento de uma thread que o criou e o utilizou.

#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