Posting a POSIX Semaphore sem_post() โ€“ Incrementing a Semaphore to Signal Resource Availability

 

Posting a POSIX Semaphore
sem_post() โ€“ Incrementing a Semaphore to Signal Resource Availability
๐Ÿ“˜ Chapter 53 โ€“ TLPI
๐Ÿ”ง Section 53.3.2
๐ŸŽฏ Intermediate

What Does “Posting” Mean?

“Posting” a semaphore means incrementing its value by 1. It is the counterpart to “waiting” (decrementing). Posting signals that a resource has been released and is now available for another process or thread. If any process is currently blocked in sem_wait(), one of them will be unblocked.

Conceptually: sem_wait() = “take a token from the semaphore” and sem_post() = “put a token back into the semaphore”.

Key Terms:

sem_post() Increment Unblock Synchronisation Wakeup Real-time Scheduling Round-robin Policy Signal from Handler

1. sem_post() โ€“ Function Overview

Function Signature

#include <semaphore.h>

int sem_post(sem_t *sem);
/* Returns 0 on success, or -1 on error */

sem_post() always increments the semaphore value by exactly 1. There are two cases:

sem_post() Behaviour
Case 1: No one is waiting

Current value = N
โ†“
sem_post() called
โ†“
Value becomes N+1
โ†“
Returns 0 (success)

Case 2: Processes are blocked

Current value = 0, N waiters blocked
โ†“
sem_post() called
โ†“
One waiter is unblocked
โ†“
That waiter’s sem_wait() decrements back to 0
โ†“
Returns 0 (success)

Note: The semaphore value does not go to 1 then back to 0 “atomically” โ€” the kernel handles the wakeup and decrement as a single atomic operation.

Which Blocked Process Gets Woken Up?

When multiple processes are blocked in sem_wait() and one sem_post() is called, which one wakes up depends on scheduling:

  • Default round-robin / time-sharing: The choice is indeterminate โ€” the kernel may wake any of the waiting processes. Do not rely on any particular order.
  • Real-time scheduling (SCHED_FIFO, SCHED_RR): SUSv3 specifies that the process with the highest priority that has been waiting the longest is woken up.
โš ๏ธ Key insight: POSIX semaphores are a synchronisation mechanism, not a queuing mechanism. If you need ordered wakeup of waiting processes, you must implement that ordering yourself on top of the semaphore.

2. sem_post() is Async-Signal-Safe

One critical property of sem_post(): it is listed in the POSIX standard as async-signal-safe. This means it is safe to call from inside a signal handler.

This is important in signal-driven programs. For example, a signal handler can call sem_post() to notify the main program that a signal was received, without risking deadlock or undefined behaviour.

Code Example โ€“ Signal Handler Using sem_post()

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <semaphore.h>
#include <unistd.h>

sem_t signal_sem;

/* Signal handler: async-signal-safe โ€” only sem_post is called */
static void sigint_handler(int sig)
{
    /* Safe to call sem_post() from a signal handler */
    sem_post(&signal_sem);
}

int main(void)
{
    struct sigaction sa;

    /* Set up semaphore: value 0 means "no signal received yet" */
    if (sem_init(&signal_sem, 0, 0) == -1) {
        perror("sem_init");
        exit(EXIT_FAILURE);
    }

    /* Install signal handler for SIGINT (Ctrl+C) */
    sa.sa_handler = sigint_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0; /* Note: SA_RESTART intentionally not used */
    if (sigaction(SIGINT, &sa, NULL) == -1) {
        perror("sigaction");
        exit(EXIT_FAILURE);
    }

    printf("Running. Press Ctrl+C to trigger SIGINT...\n");

    /* Block here, waiting for a signal to be delivered */
    if (sem_wait(&signal_sem) == -1) {
        perror("sem_wait");
    } else {
        printf("\nSIGINT received via semaphore! Clean shutdown.\n");
    }

    sem_destroy(&signal_sem);
    return EXIT_SUCCESS;
}
๐Ÿ’ก Pattern: This is a common pattern called “semaphore-based signal synchronisation”. The signal handler does nothing except post to the semaphore. The main program logic waits on the semaphore. This avoids the complexity of making the main loop async-signal-safe.

3. Practical Patterns Using sem_post()

Pattern A: Mutual Exclusion (Binary Semaphore)

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

sem_t mutex;
int critical_data = 0;

void *thread_task(void *arg)
{
    int tid = *(int *)arg;

    /* Enter critical section */
    sem_wait(&mutex);
    printf("Thread %d: entering critical section (data=%d)\n",
           tid, critical_data);
    critical_data += tid;
    printf("Thread %d: leaving critical section (data=%d)\n",
           tid, critical_data);
    sem_post(&mutex); /* Exit critical section */

    return NULL;
}

int main(void)
{
    pthread_t t[4];
    int ids[4] = {1, 2, 3, 4};
    int i;

    sem_init(&mutex, 0, 1); /* Binary semaphore */

    for (i = 0; i < 4; i++)
        pthread_create(&t[i], NULL, thread_task, &ids[i]);
    for (i = 0; i < 4; i++)
        pthread_join(t[i], NULL);

    printf("Final data: %d\n", critical_data);
    sem_destroy(&mutex);
    return EXIT_SUCCESS;
}

Pattern B: Task Completion Notification

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>

sem_t task_done;
int result = 0;

void *worker(void *arg)
{
    printf("[Worker] Starting computation...\n");
    sleep(2);  /* Simulate heavy computation */
    result = 42;
    printf("[Worker] Computation complete. result=%d\n", result);

    /* Signal main thread that work is done */
    sem_post(&task_done);
    return NULL;
}

int main(void)
{
    pthread_t w;

    sem_init(&task_done, 0, 0); /* 0 = not done yet */

    pthread_create(&w, NULL, worker, NULL);

    printf("[Main] Waiting for worker to finish...\n");
    sem_wait(&task_done); /* Block until worker calls sem_post */
    printf("[Main] Worker done. Using result: %d\n", result);

    pthread_join(w, NULL);
    sem_destroy(&task_done);
    return EXIT_SUCCESS;
}

Pattern C: Semaphore Used Across Processes (Named)

/* === Process 1: Resource holder (posts when done) === */
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

int main(void)
{
    sem_t *sem;

    /* Create semaphore, initial value 0 (resource not ready yet) */
    sem = sem_open("/ipc_ready", O_CREAT, S_IRUSR | S_IWUSR, 0);
    if (sem == SEM_FAILED) { perror("sem_open"); exit(EXIT_FAILURE); }

    printf("[Process 1] Preparing resource...\n");
    sleep(3); /* Simulate preparation */
    printf("[Process 1] Resource ready! Posting semaphore.\n");

    sem_post(sem); /* Signal Process 2 */

    sem_close(sem);
    /* Note: don't unlink yet โ€” Process 2 may not have opened it */
    return EXIT_SUCCESS;
}

/* === Process 2: Waiter (waits for resource to be ready) === */
/*
int main(void)
{
    sem_t *sem;
    sem = sem_open("/ipc_ready", 0); // Open existing
    if (sem == SEM_FAILED) { perror("sem_open"); exit(EXIT_FAILURE); }

    printf("[Process 2] Waiting for resource...\n");
    sem_wait(sem); // Blocks until Process 1 calls sem_post
    printf("[Process 2] Resource is ready, proceeding!\n");

    sem_close(sem);
    sem_unlink("/ipc_ready"); // Cleanup
    return EXIT_SUCCESS;
}
*/

๐ŸŽฏ Interview Questions โ€“ sem_post()
Q1: Is sem_post() safe to call from a signal handler?

A: Yes. sem_post() is listed as async-signal-safe by POSIX. This makes it one of the few synchronisation primitives you can safely call from within a signal handler. It is commonly used to “notify” the main program loop that a signal was received, avoiding complex async-signal-safe constraints in the main logic.

Q2: What happens if sem_post() is called when no process is blocked on sem_wait()?

A: The semaphore value is simply incremented by 1. There is no error or lost notification. The next process that calls sem_wait() will find the value greater than 0 and will decrement it without blocking. This “store-and-forward” property makes POSIX semaphores useful as counters of available resources.

Q3: If 5 processes are blocked in sem_wait() and sem_post() is called once, how many wake up?

A: Exactly one process wakes up. One sem_post() increments the value by 1, which allows one waiting process to complete its decrement. To wake all 5 processes, you would need to call sem_post() 5 times.

Q4: Under normal scheduling, which blocked process is woken by sem_post()?

A: Under the default time-sharing (round-robin) scheduling policy, it is indeterminate โ€” the kernel may wake any one of the waiting processes. POSIX does not guarantee any specific order (FIFO, priority, etc.) under non-realtime scheduling. Under real-time scheduling (SCHED_FIFO/SCHED_RR), SUSv3 specifies the highest-priority process that has waited the longest is woken.

Q5: What is the difference between using a semaphore and a condition variable (pthread_cond_t) for thread notification?

A: A semaphore “remembers” a post even if no one is waiting at that moment (the value stays incremented). A condition variable does not โ€” if pthread_cond_signal() is called when no thread is waiting, the signal is lost. Semaphores are therefore better for “one-shot” or “counted” notifications. Condition variables are better when the predicate (the condition being waited on) can be re-evaluated, and are typically used with a mutex.

Q6: Can sem_post() fail? What common error can it return?

A: Yes. sem_post() can fail with:

  • EINVAL โ€” the sem argument does not refer to a valid semaphore
  • EOVERFLOW โ€” the semaphore value would exceed SEM_VALUE_MAX (the maximum value allowed)

Always check the return value of sem_post() in production code.

Leave a Reply

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