Overview of Semaphore Operations
POSIX provides four functions to operate on semaphore values. All work on both named and unnamed semaphores. They all take a pointer to the semaphore (sem_t *).
- sem_wait() — blocking decrement (P operation)
- sem_trywait() — non-blocking decrement, returns EAGAIN if value is 0
- sem_timedwait() — blocking decrement with a timeout deadline
- sem_post() — increment (V operation)
sem_wait() – Blocking Decrement
#include <semaphore.h>
int sem_wait(sem_t *sem);
/* Returns: 0 on success, -1 on error (errno = EINTR if interrupted) */
sem_wait() performs the “wait” (P) operation:
- If the semaphore value is greater than 0: decrement it by 1 and return immediately.
- If the semaphore value is 0: the caller blocks (sleeps) until another thread or process calls
sem_post().
Return 0 (success)
Wait for sem_post()
Handling EINTR in sem_wait()
/* Robust wrapper that retries on signal interruption */
int sem_wait_robust(sem_t *sem)
{
while (sem_wait(sem) == -1) {
if (errno != EINTR) {
perror("sem_wait");
return -1; /* real error */
}
/* EINTR: retry */
}
return 0;
}
sem_trywait() – Non-Blocking Decrement
#include <semaphore.h>
int sem_trywait(sem_t *sem);
/* Returns: 0 on success, -1 on error
errno = EAGAIN if value is 0 (would have blocked) */
sem_trywait() is the non-blocking variant. If the value is 0, it returns immediately with errno = EAGAIN instead of blocking. Use it when you want to attempt acquisition but do something else if the semaphore is not available.
#include <stdio.h>
#include <errno.h>
#include <semaphore.h>
void try_acquire(sem_t *sem)
{
if (sem_trywait(sem) == 0) {
printf("Acquired! Doing protected work.\n");
/* ... critical section ... */
sem_post(sem);
} else {
if (errno == EAGAIN) {
printf("Semaphore busy, doing other work.\n");
} else {
perror("sem_trywait");
}
}
}
sem_timedwait() – Timed Blocking Decrement
#include <semaphore.h>
#include <time.h>
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
/* Returns: 0 on success, -1 on error
errno = ETIMEDOUT if deadline reached without decrement */
sem_timedwait() blocks until either the semaphore can be decremented OR an absolute deadline (in CLOCK_REALTIME time) is reached.
⚠ Critical: abs_timeout is ABSOLUTE, not relative
The timeout is an absolute wall-clock time (seconds since the Unix epoch), not a duration. You must compute it yourself using clock_gettime(CLOCK_REALTIME, ...) and adding your desired timeout.
Example: Wait with 5-second timeout
#include <stdio.h>
#include <errno.h>
#include <time.h>
#include <semaphore.h>
int wait_with_timeout(sem_t *sem, int timeout_seconds)
{
struct timespec deadline;
/* Step 1: get current absolute time */
if (clock_gettime(CLOCK_REALTIME, &deadline) == -1) {
perror("clock_gettime");
return -1;
}
/* Step 2: add relative timeout to get absolute deadline */
deadline.tv_sec += timeout_seconds;
/* Step 3: call sem_timedwait */
while (sem_timedwait(sem, &deadline) == -1) {
if (errno == EINTR)
continue; /* interrupted by signal, retry */
if (errno == ETIMEDOUT) {
printf("Timeout! Semaphore not available within %d seconds.\n",
timeout_seconds);
return -1;
}
perror("sem_timedwait");
return -1;
}
return 0; /* success */
}
int main(void)
{
sem_t sem;
sem_init(&sem, 0, 0); /* starts at 0, so wait will timeout */
if (wait_with_timeout(&sem, 3) == 0)
printf("Got semaphore.\n");
else
printf("Did not get semaphore.\n");
sem_destroy(&sem);
return 0;
}
sem_post() – Increment (Release)
#include <semaphore.h>
int sem_post(sem_t *sem);
/* Returns: 0 on success, -1 on error */
sem_post() increments the semaphore value by 1. If any threads or processes are blocked in sem_wait() on this semaphore, exactly one of them is woken up (which one is unspecified — up to the scheduler).
Key property: sem_post() is async-signal-safe
sem_post() is one of the few synchronization functions that can safely be called from a signal handler. This makes semaphores useful for signaling from a signal handler to a waiting thread.
sem_post() in a Signal Handler
#include <stdio.h>
#include <signal.h>
#include <semaphore.h>
sem_t sig_sem;
void sigint_handler(int sig)
{
/* Safe to call sem_post from signal handler */
sem_post(&sig_sem);
}
int main(void)
{
sem_init(&sig_sem, 0, 0);
signal(SIGINT, sigint_handler);
printf("Press Ctrl+C to continue...\n");
sem_wait(&sig_sem); /* block until Ctrl+C */
printf("Signal received, continuing.\n");
sem_destroy(&sig_sem);
return 0;
}
Operations Summary
| Function | Value = 0 ? | Value > 0 ? | Signal-Safe? |
|---|---|---|---|
sem_wait() |
Blocks until post | Decrements, returns 0 | No |
sem_trywait() |
Returns EAGAIN immediately | Decrements, returns 0 | No |
sem_timedwait() |
Blocks until post or deadline | Decrements, returns 0 | No |
sem_post() |
Increments to 1, wakes waiter | Increments by 1 | YES |
Full Example: Producer-Consumer with Semaphores
Classic producer-consumer using three semaphores: one to track empty slots, one to track filled slots, and one mutex to protect the buffer.
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 5
#define ITEMS 10
int buffer[BUFFER_SIZE];
int in_pos = 0;
int out_pos = 0;
sem_t empty_slots; /* tracks empty spaces in buffer */
sem_t filled_slots; /* tracks filled spaces in buffer */
sem_t mutex; /* protects buffer access */
void *producer(void *arg)
{
for (int i = 0; i < ITEMS; i++) {
sem_wait(&empty_slots); /* wait for an empty slot */
sem_wait(&mutex); /* lock buffer */
buffer[in_pos] = i;
in_pos = (in_pos + 1) % BUFFER_SIZE;
printf("Produced: %d\n", i);
sem_post(&mutex); /* unlock buffer */
sem_post(&filled_slots); /* signal a filled slot */
}
return NULL;
}
void *consumer(void *arg)
{
for (int i = 0; i < ITEMS; i++) {
sem_wait(&filled_slots); /* wait for a filled slot */
sem_wait(&mutex); /* lock buffer */
int val = buffer[out_pos];
out_pos = (out_pos + 1) % BUFFER_SIZE;
printf(" Consumed: %d\n", val);
sem_post(&mutex); /* unlock buffer */
sem_post(&empty_slots); /* signal an empty slot */
}
return NULL;
}
int main(void)
{
pthread_t prod_tid, cons_tid;
sem_init(&empty_slots, 0, BUFFER_SIZE); /* all slots empty initially */
sem_init(&filled_slots, 0, 0); /* no filled slots */
sem_init(&mutex, 0, 1); /* binary mutex */
pthread_create(&prod_tid, NULL, producer, NULL);
pthread_create(&cons_tid, NULL, consumer, NULL);
pthread_join(prod_tid, NULL);
pthread_join(cons_tid, NULL);
sem_destroy(&empty_slots);
sem_destroy(&filled_slots);
sem_destroy(&mutex);
return 0;
}
gcc producer_consumer.c -o prod_cons -lpthread
./prod_cons
