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”.
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 |
Case 2: Processes are blocked
Current value = 0, N waiters blocked |
| 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.
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 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;
}
*/
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.
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.
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.
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.
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.
A: Yes. sem_post() can fail with:
EINVALโ thesemargument does not refer to a valid semaphoreEOVERFLOWโ the semaphore value would exceedSEM_VALUE_MAX(the maximum value allowed)
Always check the return value of sem_post() in production code.
