How to Use a Semaphore in C

Jinku Hu Feb 02, 2024
  1. Use POSIX Semaphores to Synchronize Access to Shared Variables in C
  2. Use the sem_destroy Function to Destroy Unnamed Semaphore
How to Use a Semaphore in C

This article will demonstrate multiple methods about how to use a semaphore in C.

Use POSIX Semaphores to Synchronize Access to Shared Variables in C

There are two common semaphore APIs on UNIX-based systems - POSIX semaphores and System V semaphores. The latter is considered to have a less simple interface while offering the same features as POSIX API. Note that semaphores are yet another synchronization mechanism like mutexes and can be utilized in mostly similar scenarios. A semaphore is an integer maintained by the kernel, usually set to the initial value greater or equal to 0.

Two operations can be done on a semaphore object - increment or decrement by one, which corresponds to acquiring and releasing the shared resource. POSIX provides a special sem_t type for an unnamed semaphore, a more common tool in multi-threaded workflows. sem_t variable must be initialized with the sem_init function that also indicates whether the given semaphore should be shared between processes or threads of a process. Once the variable is initialized, we can implement the synchronization using the functions sem_post and sem_wait. sem_post increments the semaphore, which usually corresponds to unlocking the shared resource. In contrast, sem_wait decrements the semaphore and denotes the locking of the resource. Thus, the critical section would need to start with sem_wait and end with sem_post call. Mind though, that checking for success status code can be essential to debugging the 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);
}

Sample Command:

./program_name 1000

Output:

shared = 4000

Use the sem_destroy Function to Destroy Unnamed Semaphore

A semaphore initialized with a sem_init call must be destroyed using the sem_destroy function. Note though that sem_destroy should be called when none of the processes/threads are waiting for it. Omitting the sem_destroy call may result in a memory leak on some systems.

Generally, the semaphores have a similar performance compared to the Pthread mutexes, but the latter is usually preferred for better code structure. Although, there are some scenarios where the lock should be modified from the signal handler, which requires the function to be async-safe, and only sem_post is implemented as such. There is also a named semaphore in POSIX API, that may persist even after a thread that created it and used it, terminates.

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