C++ / a working model

135 / 163   ·   C11   ·   8 min

Semaphores

Keep this sentence

A semaphore coordinates threads with an integer counter plus blocking and wakeup primitives. Its initial value decides whether it behaves as a mutex or an event notifier. This chapter uses original examples to show wait/post semantics, binary usage, and parent-child ordering, plus a compilable C demo.

In this lesson
  1. Counter Model and Two Operations
  2. Using a Semaphore as a Lock
  3. Enforcing Order with Semaphores
  4. Example
  5. Exercise

Official chapter PDF

Counter Model and Two Operations

A semaphore stores an integer. Wait subtracts one; if the result becomes negative the caller is placed on a sleep queue. Post adds one and, if the queue is nonempty, wakes one sleeper. The absolute value of a negative count equals the number of sleeping threads. All updates must occur inside an atomic critical section, so a real implementation typically also needs a low-level lock.

Using a Semaphore as a Lock

Setting the counter to 1 yields mutual exclusion. The first wait succeeds and changes the count to 0; later waits block. The matching post restores the count to 1 and may wake a contender. Because only the two states “held/not held” are visible, this usage is often called a binary semaphore. Keep the critical section short to preserve concurrency.

Enforcing Order with Semaphores

Setting the counter to 0 lets one thread wait for a completion signal from another. The child posts after finishing its work; the parent blocks on wait until that signal arrives. The pattern avoids busy-waiting and is lighter than a condvar because no extra predicate check is required. Wake-up order among multiple waiters is implementation-defined, commonly FIFO.

Pitfalls

  • Using a semaphore without calling sem_init is undefined behaviour.
  • Initialising a lock semaphore to 0 makes the first thread block forever.

Run an example

Minimum C11 · complete program · Download .c

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define N 1000

sem_t lock;
int shared = 0;

void *inc(void *arg) { (void)arg;
    for (int i = 0; i < N; i++) {
        sem_wait(&lock);
        shared++;
        sem_post(&lock);
    }
    return NULL;
}

int main(void) {
    pthread_t t[2];
    sem_init(&lock, 0, 1);
    for (int i = 0; i < 2; i++) {
        pthread_create(&t[i], NULL, inc, NULL);
    }
    for (int i = 0; i < 2; i++) {
        pthread_join(t[i], NULL);
    }
    printf("%d\n", shared);
    sem_destroy(&lock);
    return 0;
}

Compile locally

gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -pthread ostep-31-semaphores.c -o example && ./example

Expected result

2000

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

What initial value must a semaphore have to protect a critical section? What happens if it is wrongly set to 0?

Show a reference answer

It must be 1. If it is 0 the first wait changes the count to -1 and sleeps, so the critical section is never entered.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog