What are Unnamed Semaphores?
Unnamed semaphores have no name — they live in a memory location (a sem_t variable). They are created and initialized with sem_init() and cleaned up with sem_destroy(). Because they have no filesystem name, they cannot be accessed by unrelated processes.
They can be shared between:
- Threads (most common): place the
sem_tin a global variable or heap, setpshared = 0 - Related processes (parent + children): place the
sem_tin a shared memory region (mmap, POSIX shm, or SysV shm), setpshared = 1
When to Use Named vs Unnamed Semaphores
| Scenario | Use | Why |
|---|---|---|
| Synchronizing threads in the same process | Unnamed (pshared=0) | Simpler, no name needed, lives in global/heap |
| Parent & child sharing after fork() | Unnamed (pshared=1) in shared mem | No cleanup of names, faster setup |
| Synchronizing two unrelated processes | Named semaphore | No shared memory needed; name is the rendezvous point |
| Temporary sync within a function | Unnamed (local sem_t) | Stack-allocated, no global name pollution |
sem_init() — Initialize an Unnamed Semaphore
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
/* Returns: 0 on success, -1 on error */
| Parameter | Description |
|---|---|
sem |
Pointer to a sem_t variable that will be initialized |
pshared |
0 = shared between threads of this process only non-zero = shared between processes (must be in shared memory) |
value |
Initial value of the semaphore (e.g., 1 for binary, N for counting N resources) |
sem_t variable passed to sem_init() must remain at the same memory address for the entire lifetime of the semaphore. Do NOT move it, reallocate it, or copy it after calling sem_init().sem_destroy() — Clean Up an Unnamed Semaphore
#include <semaphore.h>
int sem_destroy(sem_t *sem);
/* Returns: 0 on success, -1 on error */
sem_destroy() destroys an unnamed semaphore, freeing any resources the kernel may have associated with it. After calling it, the sem_t variable is uninitialized and must not be used unless re-initialized.
sem_destroy() on a semaphore that has threads blocked in sem_wait(). The result is undefined behavior. Always ensure no thread is waiting before destroying.Where to Place the sem_t Variable
Example 1: Thread Synchronization with Unnamed Semaphore
The most common use case: synchronizing multiple threads in the same process.
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
/* Global unnamed semaphore shared by all threads */
static sem_t mutex_sem;
static int shared_data = 0;
void *writer_thread(void *arg)
{
int id = *(int *)arg;
for (int i = 0; i < 5; i++) {
sem_wait(&mutex_sem); /* acquire lock */
shared_data++;
printf("Thread %d: wrote shared_data = %d\n", id, shared_data);
sem_post(&mutex_sem); /* release lock */
}
return NULL;
}
int main(void)
{
pthread_t t1, t2, t3;
int ids[3] = {1, 2, 3};
/* Initialize unnamed semaphore:
* pshared=0 (thread-shared), value=1 (binary/mutex) */
if (sem_init(&mutex_sem, 0, 1) == -1) {
perror("sem_init");
exit(EXIT_FAILURE);
}
/* Create 3 threads */
pthread_create(&t1, NULL, writer_thread, &ids[0]);
pthread_create(&t2, NULL, writer_thread, &ids[1]);
pthread_create(&t3, NULL, writer_thread, &ids[2]);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
printf("Final shared_data (should be 15): %d\n", shared_data);
/* Destroy when no longer needed */
sem_destroy(&mutex_sem);
return 0;
}
Example 2: Producer-Consumer with Unnamed Semaphores
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define SLOTS 4
#define ITEMS 12
static sem_t sem_empty; /* counts empty slots */
static sem_t sem_full; /* counts filled slots */
static sem_t sem_mutex; /* protects buffer */
static int buffer[SLOTS];
static int in_pos = 0, out_pos = 0;
void *producer(void *arg)
{
for (int i = 0; i < ITEMS; i++) {
sem_wait(&sem_empty);
sem_wait(&sem_mutex);
buffer[in_pos] = i * 10;
printf("[Producer] Added %d at slot %d\n", i * 10, in_pos);
in_pos = (in_pos + 1) % SLOTS;
sem_post(&sem_mutex);
sem_post(&sem_full);
}
return NULL;
}
void *consumer(void *arg)
{
for (int i = 0; i < ITEMS; i++) {
sem_wait(&sem_full);
sem_wait(&sem_mutex);
int item = buffer[out_pos];
printf("[Consumer] Got %d from slot %d\n", item, out_pos);
out_pos = (out_pos + 1) % SLOTS;
sem_post(&sem_mutex);
sem_post(&sem_empty);
}
return NULL;
}
int main(void)
{
pthread_t prod, cons;
/* Initialize all three semaphores */
sem_init(&sem_empty, 0, SLOTS); /* SLOTS empty slots initially */
sem_init(&sem_full, 0, 0); /* 0 full slots initially */
sem_init(&sem_mutex, 0, 1); /* mutex: initially unlocked */
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
/* Cleanup */
sem_destroy(&sem_empty);
sem_destroy(&sem_full);
sem_destroy(&sem_mutex);
return 0;
}
Example 3: Process-Shared Semaphore via mmap()
For sharing between a parent and child process, place the semaphore in anonymous shared memory using mmap() with MAP_SHARED | MAP_ANONYMOUS.
#include <semaphore.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
sem_t *sem;
pid_t pid;
/*
* Allocate shared memory for the sem_t variable.
* MAP_SHARED + MAP_ANONYMOUS: visible to parent AND child after fork.
*/
sem = mmap(NULL, sizeof(sem_t),
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
if (sem == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
/*
* Initialize semaphore IN the shared memory.
* pshared=1: process-shared
* value=0: starts locked (parent waits for child)
*/
if (sem_init(sem, 1, 0) == -1) {
perror("sem_init");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) { perror("fork"); exit(1); }
if (pid == 0) {
/* CHILD: do work, then signal parent */
printf("Child (pid %d): doing computation...\n", getpid());
sleep(2);
printf("Child: finished, signaling parent\n");
sem_post(sem); /* wake parent */
exit(0);
} else {
/* PARENT: wait for child to finish */
printf("Parent: waiting for child to finish...\n");
sem_wait(sem);
printf("Parent: child finished! Continuing.\n");
wait(NULL);
/* Cleanup */
sem_destroy(sem);
munmap(sem, sizeof(sem_t));
}
return 0;
}
Example 4: sem_t Embedded in a Struct
A common pattern is embedding a semaphore directly inside a data structure that the semaphore protects.
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUF_SIZE 256
/* A thread-safe message queue with embedded semaphore */
typedef struct {
char data[BUF_SIZE];
int has_message; /* 1 if a message is waiting */
sem_t sem; /* semaphore embedded in struct */
} msg_box_t;
msg_box_t mailbox;
void *sender(void *arg)
{
/* Wait until mailbox is empty */
/* (in a full implementation you'd have a second sem for full/empty) */
sleep(1);
strncpy(mailbox.data, "Hello from sender!", BUF_SIZE - 1);
mailbox.has_message = 1;
printf("Sender: message placed\n");
sem_post(&mailbox.sem); /* signal receiver */
return NULL;
}
void *receiver(void *arg)
{
printf("Receiver: waiting for message...\n");
sem_wait(&mailbox.sem); /* wait for message */
printf("Receiver: got message: '%s'\n", mailbox.data);
return NULL;
}
int main(void)
{
pthread_t s, r;
/* Initialize semaphore IN the struct: pshared=0, value=0 */
if (sem_init(&mailbox.sem, 0, 0) == -1) {
perror("sem_init");
exit(1);
}
mailbox.has_message = 0;
pthread_create(&r, NULL, receiver, NULL);
pthread_create(&s, NULL, sender, NULL);
pthread_join(r, NULL);
pthread_join(s, NULL);
sem_destroy(&mailbox.sem);
return 0;
}
Common Errors with Unnamed Semaphores
| Mistake | Consequence | Fix |
|---|---|---|
Using sem_t without calling sem_init() |
Undefined behavior (garbage value) | Always call sem_init() first |
| pshared=1 but sem_t is on the stack | Child sees different memory — sync broken | Place in mmap/shm shared memory |
Calling sem_destroy() while thread is waiting |
Undefined behavior | Join all threads first, then destroy |
Copying sem_t with = |
Undefined behavior on the copy | Always use the original variable’s address |
Forgetting sem_destroy() |
Resource leak on some systems | Always destroy when done |
Chapter Summary: All POSIX Semaphore Functions
| Function | Named | Unnamed | Purpose |
|---|---|---|---|
sem_open() |
✅ | ❌ | Create/open by name |
sem_init() |
❌ | ✅ | Initialize in-memory semaphore |
sem_post() |
✅ | ✅ | Increment (unlock/signal) |
sem_wait() |
✅ | ✅ | Decrement, block if 0 |
sem_trywait() |
✅ | ✅ | Non-blocking decrement |
sem_timedwait() |
✅ | ✅ | Decrement with timeout |
sem_getvalue() |
✅ | ✅ | Read current value (debug) |
sem_close() |
✅ | ❌ | Close process’s reference |
sem_unlink() |
✅ | ❌ | Delete named semaphore |
sem_destroy() |
❌ | ✅ | Destroy unnamed semaphore |
🅾 Interview Questions & Answers
Q1. What does the pshared parameter in sem_init() control?
If pshared = 0, the semaphore is shared only among threads of the current process (the sem_t can live anywhere accessible to those threads). If pshared != 0 (non-zero), the semaphore is shared between processes — but the sem_t variable MUST reside in shared memory (mmap, POSIX shm, or SysV shm) visible to all cooperating processes.
Q2. Why must a process-shared unnamed semaphore (pshared=1) be in shared memory?
Because each process has its own private virtual address space. If the sem_t is in the parent’s stack or heap, the child after fork() would have a copy in its own address space — changes by one process would not be seen by the other. Shared memory regions are mapped to the same physical memory pages in both processes’ address spaces, so both see the same sem_t.
Q3. What is the right way to create a shared memory region for an unnamed process-shared semaphore?
Use mmap() with MAP_SHARED | MAP_ANONYMOUS flags and fd = -1. This creates a shared anonymous mapping. After fork(), parent and child both see the same physical pages. Alternatively, use shm_open() + mmap() for unrelated processes.
Q4. When should you prefer unnamed semaphores over named semaphores?
When all cooperating threads/processes already share memory (e.g., threads in the same process, or parent+child after fork with shared mmap). Unnamed semaphores avoid the overhead of a filesystem name and don’t require sem_close() + sem_unlink() cleanup. Use named semaphores when you need unrelated processes to synchronize and don’t have a pre-existing shared memory region.
Q5. What happens if you call sem_destroy() while a thread is blocked in sem_wait()?
Undefined behavior. On Linux it may immediately return EBUSY, or may corrupt memory. The safe approach: ensure all threads have exited or been unblocked (post enough times) before calling sem_destroy(). Typically you pthread_join() all threads before destroying semaphores in main().
Q6. Can an unnamed semaphore be used between two completely unrelated processes?
Yes, but only if both processes can map the same shared memory region. You would create a POSIX shared memory object with shm_open(), place the sem_t at the start, initialize with sem_init(pshared=1), and both processes mmap the same shared object. In practice, for unrelated processes, named semaphores are simpler and more commonly used.
Q7. What is the advantage of embedding sem_t inside a struct?
It collocates the lock with the data it protects, making the design clearer and reducing the risk of using the wrong semaphore for a given resource. It also simplifies passing both the data and its lock together as a single pointer. This pattern is standard in object-oriented C design for thread-safe data structures.
You’ve Completed Chapter 53!
Now you know both types of POSIX semaphores and all their operations. Practice with the code examples above.
