100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C

Mutex and Semaphores

Compare mutexes and binary/counting semaphores as tools for enforcing mutual exclusion and coordinating threads.

Process SynchronizationIntermediate11 min readJul 8, 2026
Analogies

Introduction

Mutexes and semaphores are the two most widely used low-level synchronization primitives for protecting critical sections and coordinating the execution order of threads or processes. Although they are often used interchangeably in casual conversation, they have different semantics, different intended use cases, and different correctness guarantees, and mixing them up is a common source of subtle bugs.

🏏

Cricket analogy: A single stump camera (mutex) versus a shared pool of three practice balls (semaphore) look similar as 'shared cricket gear' but behave differently — conflating them, like assuming the camera can be shared by three people at once, causes real problems.

Requirements

A mutex (mutual exclusion lock) is a locking primitive with ownership semantics: exactly one thread can hold the lock at a time, and — on POSIX systems — the same thread that locked it is normally the one expected to unlock it. A mutex is a binary construct used purely for protecting a critical section. A semaphore, by contrast, is an integer counter accessed only through two atomic operations, wait() (also called P() or sem_wait()) which decrements the counter and blocks if it is negative/zero, and signal() (also called V() or sem_post()) which increments the counter and wakes a waiter. A binary semaphore behaves similarly to a mutex but has no ownership concept — any thread may call sem_post(), even one that never called sem_wait(), which makes semaphores well suited for signaling between different threads (e.g., producer signals consumer). A counting semaphore generalizes this to manage a pool of N interchangeable resources, initialized to N, so up to N threads can proceed concurrently before further callers block.

🏏

Cricket analogy: A mutex is like a bat that only the batsman who picked it up may return to the kit bag (ownership); a semaphore is like a scoreboard counter that any fielder can increment, and a counting semaphore of 3 is like exactly three spare balls available for any bowler to grab until they're gone.

Example

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

#define NUM_THREADS 4
#define INCREMENTS  100000

long counter = 0;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
sem_t sem; /* binary semaphore, used the same way here for comparison */

void *increment_with_mutex(void *arg) {
    for (int i = 0; i < INCREMENTS; i++) {
        pthread_mutex_lock(&lock);
        counter++;                 /* critical section */
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

void *increment_with_semaphore(void *arg) {
    for (int i = 0; i < INCREMENTS; i++) {
        sem_wait(&sem);             /* P(): acquire */
        counter++;                  /* critical section */
        sem_post(&sem);             /* V(): release */
    }
    return NULL;
}

int main(void) {
    pthread_t threads[NUM_THREADS];
    sem_init(&sem, 0, 1);          /* 1 permit => acts like a mutex */

    counter = 0;
    for (int i = 0; i < NUM_THREADS; i++)
        pthread_create(&threads[i], NULL, increment_with_mutex, NULL);
    for (int i = 0; i < NUM_THREADS; i++)
        pthread_join(threads[i], NULL);
    printf("Mutex result:     %ld (expected %d)\n", counter, NUM_THREADS * INCREMENTS);

    counter = 0;
    for (int i = 0; i < NUM_THREADS; i++)
        pthread_create(&threads[i], NULL, increment_with_semaphore, NULL);
    for (int i = 0; i < NUM_THREADS; i++)
        pthread_join(threads[i], NULL);
    printf("Semaphore result: %ld (expected %d)\n", counter, NUM_THREADS * INCREMENTS);

    pthread_mutex_destroy(&lock);
    sem_destroy(&sem);
    return 0;
}

Output

Both versions print Mutex result: 400000 (expected 400000) and Semaphore result: 400000 (expected 400000) every time they are run, because both pthread_mutex_lock/unlock and sem_wait/sem_post make the increment atomic with respect to other threads. The key difference is not in this toy example but in intent and generality: a mutex is the right tool for protecting a critical section within one logical resource, while a semaphore initialized with N > 1 is the right tool for limiting concurrent access to a pool of N resources, or for signaling events between threads (e.g., a consumer waiting on a semaphore that a producer posts to), something a mutex is not designed to do since it has no notion of 'signal from another thread' baked into correct usage.

🏏

Cricket analogy: Whether the scorer uses a manual tally sheet with a locked pen (mutex) or a digital counter app (semaphore), the final run total is identical, 400,000; the real difference shows up in a full match, where a semaphore of 3 spare balls fits situations a single locked pen never could.

Key Takeaways

  • A mutex has ownership: typically only the locking thread should unlock it; it is meant purely for mutual exclusion.
  • A binary semaphore (initialized to 1) can enforce mutual exclusion like a mutex but has no ownership — any thread can post to it.
  • A counting semaphore (initialized to N) allows up to N concurrent holders, making it ideal for resource pools.
  • Semaphores are also used for signaling/event notification between threads, not just exclusion.
  • POSIX APIs: pthread_mutex_lock/unlock for mutexes; sem_wait (P) and sem_post (V) for semaphores; sem_init sets the starting count.

Practice what you learned

Was this page helpful?

Topics covered

#OperatingSystemsStudyNotes#OperatingSystems#MutexAndSemaphores#Mutex#Semaphores#Requirements#Example#StudyNotes#SkillVeris#ExamPrep