POSIX vs System V Semaphores Comparison, Performance & When to Use Which

 

POSIX vs System V Semaphores
Chapter 53 – Part 6 of 6 | Comparison, Performance & When to Use Which

Two Semaphore APIs on Linux

Linux provides two entirely separate semaphore interfaces: the older System V semaphores (introduced in Unix System V in the 1980s) and the newer POSIX semaphores (standardized in POSIX.1b, 1993). Both are fully supported on modern Linux. Understanding the differences helps you choose the right tool and answer interview questions confidently.

API Side-by-Side Comparison

Aspect System V Semaphores POSIX Semaphores
Header <sys/sem.h> <semaphore.h>
Create semget(key, nsems, flags) sem_open(name, ...) or sem_init()
Unit of allocation Array of semaphores (a “set”) Individual semaphore
Wait (decrement) semop(semid, sops, nsops) sem_wait()
Post (increment) semop(semid, sops, nsops) sem_post()
Operate by N Yes — sem_op can be any integer No — always ±1 only
Atomic multi-semaphore ops Yes — semop on array No — one at a time
Undo on process exit Yes — SEM_UNDO flag No built-in undo
Kernel limit Yes — SEMMNI, SEMMSL, SEMMNS Named: limited by /dev/shm space
Unnamed: limited by memory
Inspect with shell ipcs -s, ipcrm -s ls /dev/shm/, rm /dev/shm/sem.*
Portability Widely available, POSIX XSI extension POSIX.1b — slightly less portable (macOS has bugs with named sems)
API complexity Complex — semget, semctl, semop with structs Simple — small set of clean functions
Performance (uncontended) Slower — always a syscall Faster — can use futex, no syscall when uncontended

Performance: Why POSIX Wins on Uncontended Cases

On Linux, POSIX semaphores (and pthreads mutexes) are implemented using futexes (fast userspace mutexes). A futex only makes a kernel system call when there is actual contention. When the semaphore is free (value > 0 for wait, or no waiters for post), the operation is handled entirely in userspace using atomic CPU instructions — no kernel involvement at all.

System V semaphores always go through a kernel system call (semop()), even when uncontended. This makes them significantly slower in tight loops.

Uncontended Semaphore Operation Path
System V semop()
1. User calls semop()
↓ always
2. syscall → kernel
3. kernel modifies semaphore
4. return to user
~100–300 ns per op (syscall overhead)
POSIX sem_wait/post (futex)
1. User calls sem_wait()
↓ uncontended
2. atomic CAS in userspace
↓ success
3. return (no kernel call)
kernel only if contended
~5–20 ns per op (no syscall)

Benchmark Example: Measuring the Difference

This program (inspired by TLPI Exercise 53-4) measures POSIX semaphore increment+decrement throughput. Run a similar program with semop() to compare.

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

#define ITERATIONS 1000000

int main(void)
{
    sem_t sem;
    struct timespec start, end;
    long elapsed_ns;

    sem_init(&sem, 0, 1);

    clock_gettime(CLOCK_MONOTONIC, &start);

    for (int i = 0; i < ITERATIONS; i++) {
        sem_wait(&sem);
        sem_post(&sem);
    }

    clock_gettime(CLOCK_MONOTONIC, &end);

    elapsed_ns = (end.tv_sec - start.tv_sec) * 1000000000L
               + (end.tv_nsec - start.tv_nsec);

    printf("POSIX semaphore: %d iterations in %ld ms\n",
           ITERATIONS, elapsed_ns / 1000000);
    printf("Average: %.1f ns per wait+post pair\n",
           (double)elapsed_ns / ITERATIONS);

    sem_destroy(&sem);
    return 0;
}
/* Typical output on Linux:
   POSIX semaphore: 1000000 iterations in 12 ms
   Average: 12.3 ns per wait+post pair */

System V Only: SEM_UNDO — Automatic Rollback on Process Exit

One feature unique to System V semaphores is the SEM_UNDO flag for semop(). When used, the kernel records each operation and automatically undoes it when the process exits — even if the process crashes. This prevents semaphores from being left in a locked state.

POSIX semaphores have no equivalent. If a process dies holding a POSIX semaphore, the semaphore stays in its current state. Other processes waiting on it will block forever. This must be handled at the application level (e.g., using sem_timedwait with a timeout, or a watchdog process).

/* System V SEM_UNDO example */
#include <sys/sem.h>

struct sembuf op;
op.sem_num = 0;
op.sem_op  = -1;          /* decrement (lock) */
op.sem_flg = SEM_UNDO;    /* undo on exit: kernel restores value if we crash */
semop(semid, &op, 1);

/* ... critical section ... */

op.sem_op  = 1;           /* increment (unlock) */
op.sem_flg = SEM_UNDO;
semop(semid, &op, 1);

When to Use Which

Use POSIX Semaphores when:
  • Synchronizing threads within one process
  • You want a clean, simple API
  • Performance matters (uncontended hot path)
  • New code — greenfield development
  • The semaphore is tied to a short-lived resource
Use System V Semaphores when:
  • You need SEM_UNDO (crash-safe locking)
  • You need atomic operations on multiple semaphores at once
  • You need increment/decrement by values other than 1
  • Maintaining legacy code that already uses SysV IPC
  • Maximum portability to very old Unix systems

POSIX Semaphore vs POSIX Mutex (pthread_mutex)

For thread synchronization within a single process, pthread mutexes are generally preferred over semaphores. Here is why:

Feature pthread_mutex POSIX Semaphore
Ownership Yes — only locker can unlock No — anyone can post
Error detection EDEADLK, EPERM with error-checking type None
Priority inheritance Available (PTHREAD_PRIO_INHERIT) Not available
Best use case Protecting critical sections Signaling between threads/processes, counting resources
Signal handler safe No sem_post() only
Cross-process use Yes (PTHREAD_PROCESS_SHARED) Yes (named or pshared unnamed)

Complete Example: System V vs POSIX for the Same Problem

Both programs solve the same problem: a simple binary lock around a counter. Compare the API complexity.

System V version

#include <stdio.h>
#include <sys/sem.h>
#include <sys/ipc.h>

/* Required union for semctl */
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int main(void)
{
    int semid;
    struct sembuf lock_op   = { 0, -1, 0 };
    struct sembuf unlock_op = { 0,  1, 0 };
    union semun arg;

    /* Create a set of 1 semaphore */
    semid = semget(IPC_PRIVATE, 1, IPC_CREAT | 0600);
    if (semid == -1) { perror("semget"); return 1; }

    /* Set initial value to 1 */
    arg.val = 1;
    semctl(semid, 0, SETVAL, arg);

    /* Lock */
    semop(semid, &lock_op, 1);
    printf("Inside critical section.\n");
    /* Unlock */
    semop(semid, &unlock_op, 1);

    /* Remove semaphore */
    semctl(semid, 0, IPC_RMID);
    return 0;
}

POSIX version — same logic, far simpler

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

int main(void)
{
    sem_t sem;

    sem_init(&sem, 0, 1);   /* initial value = 1 */

    sem_wait(&sem);          /* lock */
    printf("Inside critical section.\n");
    sem_post(&sem);          /* unlock */

    sem_destroy(&sem);
    return 0;
}

Chapter 53 Summary

  • POSIX semaphores come in two flavors: named (filesystem-based, sem_open/close/unlink) and unnamed (memory-based, sem_init/destroy).
  • All operations (sem_wait, sem_trywait, sem_timedwait, sem_post) work on both types via a sem_t *.
  • sem_timedwait() takes an absolute CLOCK_REALTIME timestamp — always compute it with clock_gettime() plus your offset.
  • sem_post() is async-signal-safe; sem_wait() is not.
  • sem_getvalue() is for diagnostics only — never use it to make synchronization decisions.
  • SEM_VALUE_MAX is at least 32,767 (POSIX) and INT_MAX on Linux.
  • POSIX semaphores are simpler and faster than System V semaphores for most use cases. System V wins only when you need SEM_UNDO or atomic multi-semaphore operations.
  • For thread synchronization within one process, prefer pthread_mutex_t over semaphores — better error detection, ownership semantics, and priority inheritance support.

Interview Questions – POSIX vs System V & Chapter Summary

Q1. What is the biggest practical advantage of POSIX semaphores over System V semaphores?
Simpler API and better performance. POSIX semaphores use futexes on Linux, so uncontended operations (the common case) are handled entirely in userspace without a kernel syscall. System V semop() always makes a syscall. POSIX also has a cleaner, more readable interface — sem_wait/sem_post vs semop() with struct sembuf arrays.
Q2. What is the SEM_UNDO flag in System V semaphores, and does POSIX have an equivalent?
SEM_UNDO tells the kernel to record each semop adjustment and automatically reverse it if the process exits abnormally. This prevents semaphores being left permanently locked after a crash. POSIX semaphores have no equivalent — if a process dies holding a POSIX semaphore, it stays decremented. The application must handle this (e.g., sem_timedwait with a timeout, or a watchdog).
Q3. Why are mutexes generally preferred over semaphores for protecting critical sections in multithreaded code?
Mutexes have ownership semantics (only the locking thread can unlock), which enables error-checking (EDEADLK, EPERM), priority inheritance to avoid priority inversion, and recursive locking types. Semaphores have no ownership, so any thread can call sem_post — this is useful for signaling but makes bugs harder to catch in mutual exclusion scenarios.
Q4. A process acquires a named POSIX semaphore and then crashes. What happens?
The semaphore value stays at 0 (locked). The kernel does not undo the decrement. Any other process or thread blocked on sem_wait() will wait forever unless they use sem_timedwait() with a timeout. The named semaphore also persists in /dev/shm/ until explicitly unlinked.
Q5. Can you atomically operate on multiple POSIX semaphores at once?
No. POSIX semaphore functions operate on exactly one semaphore per call. System V’s semop() can atomically operate on multiple semaphores in the same set in a single call. This is one of the few functional advantages System V semaphores have over POSIX semaphores.
Q6. How do you inspect and remove leftover System V and POSIX semaphores from the command line?
System V: ipcs -s lists all semaphore sets; ipcrm -s semid removes one. POSIX named: ls /dev/shm/sem.* lists them; rm /dev/shm/sem.name or sem_unlink("/name") removes them. Unnamed semaphores have no independent existence outside their containing memory and cannot be listed.
Q7. What is a futex and why does it matter for POSIX semaphore performance?
A futex (fast userspace mutex) is a Linux kernel mechanism that allows synchronization to be performed in userspace when there is no contention. Only when a thread needs to actually sleep (because the semaphore is 0) does a syscall happen. This makes uncontended POSIX sem_wait/sem_post operate at the speed of a few atomic CPU instructions (~5–20 ns) versus ~100–300 ns for a full System V semop() syscall.
Q8. Can System V semaphore values be decremented or incremented by values other than 1?
Yes. The sem_op field in struct sembuf can be any integer. A negative value decrements (waits), a positive value increments (posts), and zero means “wait until value reaches zero.” POSIX semaphores always adjust by exactly 1 per call.

Chapter 53 Complete!

You have covered all POSIX semaphore concepts from Chapter 53 of TLPI.

← Back to Chapter 53 Index EmbeddedPathashala Home

Leave a Reply

Your email address will not be published. Required fields are marked *