What Is “Waiting” on a Semaphore?
Waiting on a semaphore means attempting to decrement its value by 1. If the semaphore value is greater than zero, the decrement happens immediately and the function returns. If the value is 0, the calling process or thread blocks until another entity increments the semaphore using sem_post().
POSIX provides three variants of the wait operation: blocking (sem_wait), non-blocking (sem_trywait), and timed (sem_timedwait).
sem_wait() is the fundamental “take” or “lock” operation. It decrements the semaphore by 1. If the semaphore is already 0, the calling process or thread blocks until the value rises above 0.
Function Signature
#include <semaphore.h>
int sem_wait(sem_t *sem);
/* Returns 0 on success, or -1 on error */
| sem_wait() Flow | ||
| Process calls sem_wait(sem) | ||
| sem value > 0 ↓ Decrement by 1 ↓ Return 0 (success) |
OR | sem value == 0 ↓ Process BLOCKS ↓ Waits for sem_post() ↓ Decrement and return 0 |
Signal Interruption – EINTR
If a signal is delivered to a process while it is blocked in sem_wait(), the call fails and returns -1 with errno set to EINTR.
This is important: even if the signal handler was installed with SA_RESTART, sem_wait() is not automatically restarted on Linux. Your code must handle EINTR by retrying the call.
Code Example – sem_wait() with EINTR Handling
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <semaphore.h>
#include <fcntl.h>
/* Helper: retry sem_wait on signal interruption */
static int safe_sem_wait(sem_t *sem)
{
int ret;
do {
ret = sem_wait(sem);
} while (ret == -1 && errno == EINTR); /* retry if interrupted by signal */
return ret;
}
int main(void)
{
sem_t *sem;
/* Open an existing semaphore */
sem = sem_open("/example_sem", 0);
if (sem == SEM_FAILED) {
perror("sem_open");
exit(EXIT_FAILURE);
}
printf("Attempting to decrement semaphore (may block if value is 0)...\n");
if (safe_sem_wait(sem) == -1) {
perror("sem_wait");
sem_close(sem);
exit(EXIT_FAILURE);
}
printf("Semaphore decremented. Entering critical section.\n");
/* ... critical section ... */
printf("Critical section done.\n");
sem_close(sem);
return EXIT_SUCCESS;
}
Code Example – Producer-Consumer with sem_wait
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int write_pos = 0;
int read_pos = 0;
sem_t empty_slots; /* counts empty slots in buffer */
sem_t full_slots; /* counts filled slots in buffer */
sem_t mutex; /* protects buffer access */
void *producer(void *arg)
{
int i;
for (i = 1; i <= 10; i++) {
sem_wait(&empty_slots); /* wait for empty slot */
sem_wait(&mutex);
buffer[write_pos % BUFFER_SIZE] = i;
write_pos++;
printf("Produced: %d\n", i);
sem_post(&mutex);
sem_post(&full_slots); /* signal new item available */
sleep(1);
}
return NULL;
}
void *consumer(void *arg)
{
int i, item;
for (i = 0; i < 10; i++) {
sem_wait(&full_slots); /* wait for an item */
sem_wait(&mutex);
item = buffer[read_pos % BUFFER_SIZE];
read_pos++;
printf("Consumed: %d\n", item);
sem_post(&mutex);
sem_post(&empty_slots); /* signal slot is free */
}
return NULL;
}
int main(void)
{
pthread_t prod, cons;
sem_init(&empty_slots, 0, BUFFER_SIZE); /* all slots empty initially */
sem_init(&full_slots, 0, 0); /* no items initially */
sem_init(&mutex, 0, 1); /* binary semaphore as mutex */
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
sem_destroy(&empty_slots);
sem_destroy(&full_slots);
sem_destroy(&mutex);
printf("Producer-consumer done.\n");
return EXIT_SUCCESS;
}
sem_trywait() is a non-blocking version of sem_wait(). If the semaphore value is greater than 0, it decrements and returns success (like sem_wait()). However, if the value is 0, it does not block — it immediately returns -1 with errno set to EAGAIN.
Use sem_trywait() when you want to attempt acquiring a semaphore without committing to wait indefinitely. This is useful in event loops, polling systems, or when you have other work to do if the semaphore is unavailable.
Function Signature
#include <semaphore.h>
int sem_trywait(sem_t *sem);
/* Returns 0 on success, or -1 on error (EAGAIN if would block) */
Code Example – sem_trywait() in a Polling Loop
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <semaphore.h>
#include <unistd.h>
sem_t resource_sem;
void do_other_work(void)
{
printf(" [doing other work while semaphore is unavailable...]\n");
usleep(200000); /* 200 ms */
}
int main(void)
{
int tries = 0;
/* Create semaphore with initial value 0 (unavailable at start) */
sem_init(&resource_sem, 0, 0);
/* Simulate: another thread/process posts after a delay */
/* For demo we post after 3 tries */
while (1) {
if (sem_trywait(&resource_sem) == 0) {
printf("Acquired semaphore on try %d!\n", tries + 1);
break;
}
if (errno == EAGAIN) {
tries++;
printf("Try %d: semaphore not available, will retry...\n", tries);
do_other_work();
/* Simulate posting after 3 failed attempts */
if (tries == 3)
sem_post(&resource_sem);
} else {
perror("sem_trywait");
break;
}
}
printf("Done.\n");
sem_destroy(&resource_sem);
return EXIT_SUCCESS;
}
sem_trywait() returns -1 for both errors (like invalid semaphore pointer) and the “would block” case. Always check errno: it is EAGAIN for the “would block” case and something else (like EINVAL) for real errors.sem_timedwait() combines the blocking behaviour of sem_wait() with a deadline. The caller blocks until either:
- The semaphore value becomes greater than 0 (success), or
- The specified timeout expires (fails with
ETIMEDOUT)
Function Signature
#define _XOPEN_SOURCE 600
#include <semaphore.h>
#include <time.h>
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
/* Returns 0 on success, or -1 on error (ETIMEDOUT if timed out) */
About abs_timeout
The timeout is an absolute time (not a relative duration) expressed as seconds and nanoseconds since the Unix Epoch (Jan 1, 1970). To specify a relative timeout (e.g., “wait up to 5 seconds”), you must:
- Get the current time using
clock_gettime(CLOCK_REALTIME, &ts) - Add your desired wait duration to the result
- Pass the resulting
struct timespectosem_timedwait()
| struct timespec fields | |
| tv_sec Seconds since Epoch time_t (long integer) |
tv_nsec Nanoseconds (0–999,999,999) long integer |
Code Example – sem_timedwait() with 5-Second Timeout
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <semaphore.h>
#include <time.h>
#define WAIT_SECONDS 5
int main(void)
{
sem_t sem;
struct timespec deadline;
/* Create semaphore with value 0 (unavailable) */
if (sem_init(&sem, 0, 0) == -1) {
perror("sem_init");
exit(EXIT_FAILURE);
}
/* Build absolute deadline: now + WAIT_SECONDS */
if (clock_gettime(CLOCK_REALTIME, &deadline) == -1) {
perror("clock_gettime");
exit(EXIT_FAILURE);
}
deadline.tv_sec += WAIT_SECONDS;
/* If adding nanoseconds that overflow into seconds: */
/* deadline.tv_nsec += extra_ns;
if (deadline.tv_nsec >= 1000000000L) {
deadline.tv_sec++;
deadline.tv_nsec -= 1000000000L;
} */
printf("Waiting up to %d seconds for semaphore...\n", WAIT_SECONDS);
if (sem_timedwait(&sem, &deadline) == -1) {
if (errno == ETIMEDOUT) {
printf("Timeout! Semaphore was not available within %d seconds.\n",
WAIT_SECONDS);
} else {
perror("sem_timedwait");
}
} else {
printf("Semaphore acquired before timeout!\n");
}
sem_destroy(&sem);
return EXIT_SUCCESS;
}
Code Example – Timed Lock with Recovery
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <semaphore.h>
#include <time.h>
#include <pthread.h>
#include <unistd.h>
sem_t db_lock; /* Protects access to a simulated "database" */
/* Helper: try to acquire semaphore within timeout_sec seconds */
int timed_lock(sem_t *s, int timeout_sec)
{
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += timeout_sec;
return sem_timedwait(s, &ts);
}
void *slow_writer(void *arg)
{
printf("[Writer] Acquiring lock...\n");
sem_wait(&db_lock);
printf("[Writer] Lock acquired, writing for 3 seconds\n");
sleep(3); /* Simulate slow write */
printf("[Writer] Done, releasing lock\n");
sem_post(&db_lock);
return NULL;
}
void *fast_reader(void *arg)
{
sleep(1); /* Start after writer */
printf("[Reader] Trying to acquire lock (2-second timeout)...\n");
if (timed_lock(&db_lock, 2) == 0) {
printf("[Reader] Got lock, reading data\n");
sem_post(&db_lock);
} else if (errno == ETIMEDOUT) {
printf("[Reader] Timed out — will retry later or use cached data\n");
} else {
perror("[Reader] sem_timedwait error");
}
return NULL;
}
int main(void)
{
pthread_t writer, reader;
sem_init(&db_lock, 0, 1); /* Binary semaphore */
pthread_create(&writer, NULL, slow_writer, NULL);
pthread_create(&reader, NULL, fast_reader, NULL);
pthread_join(writer, NULL);
pthread_join(reader, NULL);
sem_destroy(&db_lock);
return EXIT_SUCCESS;
}
sem_timedwait() was originally specified in POSIX.1d (1999). It is available on Linux but may not be present on all UNIX implementations.| Function | Blocks? | If value is 0 | Error when unavailable | Use when |
|---|---|---|---|---|
sem_wait() |
Yes | Blocks indefinitely | EINTR (signal) | You must wait until resource is available |
sem_trywait() |
No | Returns immediately | EAGAIN | Polling loops, can do other work if busy |
sem_timedwait() |
Yes, up to deadline | Blocks until timeout | ETIMEDOUT | Need a fallback/recovery after fixed wait time |
A: sem_wait() returns -1 with errno set to EINTR. Importantly, this happens even if the signal handler was installed with the SA_RESTART flag, which normally causes interrupted system calls to restart automatically. For sem_wait(), there is no auto-restart on Linux. Your code must retry the call when it encounters EINTR.
A: sem_trywait() returns -1 and sets errno to EAGAIN when the semaphore value is 0 and the operation would block. This is the standard “try again” error code, also used by non-blocking I/O operations.
A: It takes an absolute timeout as a struct timespec value representing seconds and nanoseconds since the Unix Epoch. To use a relative timeout (e.g., 5 seconds from now), you must call clock_gettime(CLOCK_REALTIME, &ts) to get the current time, then add your desired offset to produce the absolute deadline.
A:
int ret;
do {
ret = sem_wait(&sem);
} while (ret == -1 && errno == EINTR);
if (ret == -1) {
/* Real error — not a signal interruption */
perror("sem_wait");
exit(EXIT_FAILURE);
}
/* Semaphore acquired */
A: Use sem_trywait() in scenarios like: event-driven servers where blocking would stall the event loop; “try to upgrade from read lock to write lock” patterns; or when you have alternative tasks you can perform if the primary resource is busy. It avoids deadlock risks in situations where blocking could cause circular waits.
A: On timeout, sem_timedwait() returns -1 and sets errno to ETIMEDOUT. The semaphore is not decremented in this case — the caller must handle the timeout appropriately (retry, abort, use cached data, etc.).
