Waiting on a POSIX Semaphore sem_wait() · sem_trywait() · sem_timedwait() — Blocking, Non-blocking, and Timed Variants

 

Waiting on a POSIX Semaphore
sem_wait() · sem_trywait() · sem_timedwait() — Blocking, Non-blocking, and Timed Variants
📘 Chapter 53 – TLPI
🔧 Section 53.3.1
🎯 Intermediate

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).

Key Terms:

sem_wait() sem_trywait() sem_timedwait() EINTR EAGAIN ETIMEDOUT struct timespec clock_gettime() SA_RESTART

1. sem_wait() – Blocking Wait

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;
}

2. sem_trywait() – Non-blocking Wait

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;
}
⚠️ Note: 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.

3. sem_timedwait() – Timed Wait

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:

  1. Get the current time using clock_gettime(CLOCK_REALTIME, &ts)
  2. Add your desired wait duration to the result
  3. Pass the resulting struct timespec to sem_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;
}
💡 Availability: sem_timedwait() was originally specified in POSIX.1d (1999). It is available on Linux but may not be present on all UNIX implementations.

4. Summary – Choosing the Right Wait Function
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

🎯 Interview Questions – sem_wait Variants
Q1: What happens when sem_wait() is interrupted by a signal on Linux?

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.

Q2: What error does sem_trywait() return when the semaphore is already 0?

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.

Q3: Does sem_timedwait() take a relative or absolute timeout?

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.

Q4: Write a loop that calls sem_wait() and handles both EINTR and other errors correctly.

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 */
Q5: When would you prefer sem_trywait() over sem_wait() in a real system?

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.

Q6: What is the return value of sem_timedwait() on timeout?

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.).

Leave a Reply

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