What Is a Semaphore Operation?
A POSIX semaphore is essentially a non-negative integer maintained by the kernel. Processes or threads control access to shared resources by decrementing (waiting) and incrementing (posting) this integer. The kernel ensures the value never goes below zero โ if a process tries to decrement a zero-valued semaphore, it blocks until another process increments it.
In this tutorial we cover the core operations: sem_wait(), sem_trywait(), sem_timedwait(), and sem_post(). We also explain how these differ from their System V semaphore counterparts.
Both POSIX and System V provide semaphores for process/thread synchronisation. However, there are important differences in how their operations work.
| Feature | POSIX Semaphores | System V Semaphores |
|---|---|---|
| Semaphores per operation | One at a time (sem_wait/sem_post) | Multiple at a time (semop on a set) |
| Increment/Decrement amount | Exactly 1 (fixed) | Arbitrary value |
| Wait-for-zero | โ Not available | โ semop with sem_op=0 |
| API complexity | Simple and clean | More complex, harder to use correctly |
| Thread support | โ Unnamed semaphores work well with threads | โ ๏ธ Primarily designed for processes |
| Identification | Name (string) or sem_t variable | Integer key and semaphore ID |
A POSIX semaphore is just an integer. The kernel maintains this integer and enforces one rule: it can never go below zero.
| Semaphore Value State Machine | ||||
| Value = 2 | Value = 1 | Value = 0 | sem_wait() called here |
Process BLOCKS |
| sem_wait() decrements: 2โ1โ0 | At 0: next sem_wait() blocks | sem_post() unblocks | ||
A semaphore with initial value 1 acts like a mutex (binary semaphore). A semaphore with initial value N acts like a counting semaphore, allowing up to N concurrent accesses to a resource.
Code: Semaphore as a Mutex (Binary Semaphore)
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
sem_t mutex_sem;
int shared_counter = 0;
void *increment_task(void *arg)
{
int i;
for (i = 0; i < 100000; i++) {
sem_wait(&mutex_sem); /* Lock: decrement from 1 to 0 */
shared_counter++; /* Critical section */
sem_post(&mutex_sem); /* Unlock: increment from 0 to 1 */
}
return NULL;
}
int main(void)
{
pthread_t t1, t2;
/* Initialize unnamed semaphore: shared=0 (threads), initial value=1 */
if (sem_init(&mutex_sem, 0, 1) == -1) {
perror("sem_init");
exit(EXIT_FAILURE);
}
pthread_create(&t1, NULL, increment_task, NULL);
pthread_create(&t2, NULL, increment_task, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
printf("Final counter: %d (expected: 200000)\n", shared_counter);
sem_destroy(&mutex_sem);
return EXIT_SUCCESS;
}
Code: Counting Semaphore โ Limit Concurrent Access
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define MAX_CONCURRENT 3 /* At most 3 threads in the "pool" at once */
sem_t pool_sem;
void *worker(void *arg)
{
int id = *(int *)arg;
printf("Thread %d: waiting for pool slot...\n", id);
sem_wait(&pool_sem); /* Acquire a slot in the pool */
printf("Thread %d: in pool, doing work\n", id);
sleep(1); /* Simulate work */
printf("Thread %d: leaving pool\n", id);
sem_post(&pool_sem); /* Release the slot */
return NULL;
}
int main(void)
{
pthread_t threads[8];
int ids[8];
int i;
/* Allow at most MAX_CONCURRENT threads in the "pool" */
sem_init(&pool_sem, 0, MAX_CONCURRENT);
for (i = 0; i < 8; i++) {
ids[i] = i + 1;
pthread_create(&threads[i], NULL, worker, &ids[i]);
}
for (i = 0; i < 8; i++)
pthread_join(threads[i], NULL);
sem_destroy(&pool_sem);
printf("All threads done.\n");
return EXIT_SUCCESS;
}
A: The kernel guarantees that a semaphore’s value will never go below zero. If a process tries to decrement a semaphore that is already at 0, the kernel blocks the calling process until another process increments the semaphore.
A: No. sem_wait() always decrements by exactly 1, and sem_post() always increments by exactly 1. This is unlike System V’s semop() which can add or subtract arbitrary values. If you need to change by more than 1, you must call sem_post() or sem_wait() multiple times.
A: A binary semaphore has an initial value of 1 and acts like a mutex โ only one process/thread can hold it at a time (value oscillates between 0 and 1). A counting semaphore has an initial value of N and allows up to N concurrent holders. When value reaches 0, all further sem_wait() callers block.
A: It is only a synchronisation mechanism. When multiple processes are blocked on sem_wait() and a sem_post() is called, under the default round-robin scheduling policy it is indeterminate which waiting process gets unblocked. There is no FIFO queue of waiters. (Under real-time scheduling policies, the highest-priority waiter that has been waiting the longest is unblocked.)
A: Incrementing a POSIX semaphore signals that a shared resource has been released and is now available for another process or thread to use. It is the equivalent of saying “I am done with this resource, someone else can have it.”
โ Back: sem_close & sem_unlink Next: sem_wait() Variants โ
