Unnamed Semaphores sem_init() • sem_destroy() • Thread & Process Sharing

 

Unnamed Semaphores
sem_init() • sem_destroy() • Thread & Process Sharing — Chapter 53, File 5 of 5

🏠 Index | ← File 4: sem_getvalue | End of Chapter 53

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_t in a global variable or heap, set pshared = 0
  • Related processes (parent + children): place the sem_t in a shared memory region (mmap, POSIX shm, or SysV shm), set pshared = 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)
⚠ Critical Rule: The 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.

⚠ Warning: Do not call 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

Thread-Shared (pshared=0)

Place the sem_t where all threads can see it:

  • Global variable
  • Static variable
  • Heap (malloc)
  • Struct shared between threads
Process-Shared (pshared=1)

Must be in shared memory visible to all processes:

  • POSIX shared memory (shm_open() + mmap())
  • System V shared memory (shmget())
  • Anonymous mmap with MAP_SHARED + MAP_ANONYMOUS

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.

🏠 Back to Index More Tutorials

Leave a Reply

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