Unnamed Semaphores sem_init() and sem_destroy()

 

Unnamed Semaphores
Chapter 53 – Part 5 of 6 | sem_init() and sem_destroy()

What Are Unnamed Semaphores?

Unnamed semaphores (also called memory-based semaphores) are sem_t variables that exist in memory rather than having a filesystem name. They have no path in /dev/shm/ and cannot be opened by name from another process.

To share an unnamed semaphore between threads: place it in a global variable or heap memory. To share it between processes: place it in a POSIX shared memory object or a memory-mapped file.

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 Type Meaning
sem sem_t * Pointer to the sem_t variable to initialize
pshared int 0 = shared between threads of same process
Non-zero = shared between processes (must be in shared memory)
value unsigned int Initial value of the semaphore (0 to SEM_VALUE_MAX)

The pshared Flag — Thread vs Process Sharing

pshared = 0 (Thread Sharing)

The semaphore is shared between threads within the same process.

Location: global variable or heap (malloc()).

sem_t g_sem;  /* global */
sem_init(&g_sem, 0, 1);
pshared != 0 (Process Sharing)

The semaphore is shared between different processes.

Location: POSIX shared memory or mmap’d file — processes must all map the same memory region.

sem_t *sp = mmap(...);
sem_init(sp, 1, 1);

sem_destroy() – Destroy an Unnamed Semaphore

#include <semaphore.h>

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

sem_destroy() releases any resources associated with the semaphore. After this call, the sem_t variable must not be used unless re-initialized.

⚠ Do NOT destroy a semaphore while threads are waiting on it

Destroying a semaphore that has waiting threads results in undefined behavior. Always ensure no thread is blocked in sem_wait() before calling sem_destroy().

Unnamed Semaphore Lifecycle

Declare
sem_t s;
sem_init()
Initialize
sem_wait / sem_post
Use
sem_destroy()
Cleanup
No sem_close() or sem_unlink() needed — just init, use, destroy.

Example 1: Thread Synchronization (pshared=0)

A background worker thread signals the main thread when its job is done.

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

sem_t done_sem;    /* unnamed, thread-shared (pshared=0) */

void *worker(void *arg)
{
    printf("Worker: starting task...\n");
    sleep(2);      /* simulate work */
    printf("Worker: task complete.\n");
    sem_post(&done_sem);    /* signal main thread */
    return NULL;
}

int main(void)
{
    pthread_t tid;

    sem_init(&done_sem, 0, 0);   /* start at 0: main will block */

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

    printf("Main: waiting for worker...\n");
    sem_wait(&done_sem);          /* block until worker posts */
    printf("Main: worker is done, continuing.\n");

    pthread_join(tid, NULL);
    sem_destroy(&done_sem);
    return 0;
}

Example 2: Process Sharing via mmap (pshared=1)

For sharing between unrelated processes, the semaphore must live in shared memory. Here we use anonymous mmap() and fork().

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <semaphore.h>
#include <unistd.h>

int main(void)
{
    /* Allocate shared memory visible to parent and child */
    sem_t *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);
    }

    /* pshared = 1: process sharing */
    sem_init(sem, 1, 0);

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        /* Child: do work then signal parent */
        printf("Child (pid=%d): doing work...\n", getpid());
        sleep(2);
        printf("Child: posting semaphore.\n");
        sem_post(sem);
        exit(EXIT_SUCCESS);
    } else {
        /* Parent: wait for child's signal */
        printf("Parent: waiting for child...\n");
        sem_wait(sem);
        printf("Parent: received signal from child.\n");
        wait(NULL);
    }

    sem_destroy(sem);
    munmap(sem, sizeof(sem_t));
    return 0;
}
gcc process_sem.c -o process_sem -lpthread
./process_sem

Example 3: Unnamed Semaphore in POSIX Shared Memory

For truly unrelated processes (no fork relationship), place the unnamed semaphore inside a POSIX shared memory object.

shm_writer.c – Creates shared memory and semaphore

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <unistd.h>

#define SHM_NAME "/ep_shm_demo"

/* Layout of the shared memory region */
struct shared_data {
    sem_t sem;
    int   value;
};

int main(void)
{
    /* Create and size the shared memory object */
    int fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    ftruncate(fd, sizeof(struct shared_data));

    struct shared_data *shm = mmap(NULL, sizeof(struct shared_data),
                                   PROT_READ | PROT_WRITE,
                                   MAP_SHARED, fd, 0);
    close(fd);

    /* Initialize the semaphore inside shared memory, pshared=1 */
    sem_init(&shm->sem, 1, 0);
    shm->value = 42;

    printf("Writer: wrote value=%d, posting semaphore.\n", shm->value);
    sem_post(&shm->sem);

    sleep(5);   /* keep shm alive for reader */

    sem_destroy(&shm->sem);
    munmap(shm, sizeof(struct shared_data));
    shm_unlink(SHM_NAME);
    return 0;
}

shm_reader.c – Opens shared memory and waits on semaphore

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <semaphore.h>
#include <unistd.h>

#define SHM_NAME "/ep_shm_demo"

struct shared_data {
    sem_t sem;
    int   value;
};

int main(void)
{
    int fd = shm_open(SHM_NAME, O_RDWR, 0);
    struct shared_data *shm = mmap(NULL, sizeof(struct shared_data),
                                   PROT_READ | PROT_WRITE,
                                   MAP_SHARED, fd, 0);
    close(fd);

    printf("Reader: waiting for semaphore...\n");
    sem_wait(&shm->sem);
    printf("Reader: got semaphore, value = %d\n", shm->value);

    munmap(shm, sizeof(struct shared_data));
    return 0;
}

Common Pitfalls with Unnamed Semaphores

Mistake Result Fix
pshared=1 but sem_t is on the stack Undefined behavior — child gets a copy, not the same object Put the sem_t in mmap’d or shm_open’d memory
Copying sem_t with struct copy or memcpy The copy is unusable — opaque type, do not copy Always pass a pointer to the original sem_t
Calling sem_destroy while threads are waiting Undefined behavior, crash, or deadlock Join all threads before sem_destroy
Forgetting sem_init() before first use Undefined behavior (uninitialized memory) Always call sem_init before any operation

Interview Questions – Unnamed Semaphores

Q1. What is the difference between named and unnamed semaphores in terms of creation and cleanup?
Named: sem_open() creates / opens, sem_close() closes, sem_unlink() removes the name. Unnamed: sem_init() initializes, sem_destroy() cleans up. Unnamed semaphores have no filesystem presence and no cleanup of file system state.
Q2. What does the pshared argument to sem_init() control?
pshared = 0 means the semaphore is shared only between threads of the same process. pshared non-zero means it can be shared between different processes, but the sem_t must be placed in shared memory (mmap with MAP_SHARED, or a POSIX shared memory object) visible to all sharing processes.
Q3. Can you use an unnamed semaphore between two unrelated processes that were not forked from each other?
Yes, but you must place the sem_t inside a POSIX shared memory object (shm_open + mmap) or a memory-mapped file that both processes map. Both must use pshared=1. Using sem_init with pshared=1 on a stack or heap variable will not work across unrelated processes.
Q4. Why can’t you copy a sem_t variable using struct assignment?
sem_t is an opaque type whose internal representation is implementation-defined. It may contain kernel-level state or pointers that lose meaning when copied. Always pass a pointer to the original sem_t. Copying and then using the copy causes undefined behavior.
Q5. When should you use an unnamed semaphore instead of a named one?
Use unnamed semaphores when: (a) synchronization is between threads of the same process (simpler, no filesystem entry), (b) the lifetime of the semaphore is tied to the lifetime of a data structure in memory, or (c) you want to embed the semaphore directly inside a shared memory struct. Named semaphores are better when unrelated processes need to find the semaphore by a well-known name.
Q6. What happens if you call sem_init() on an already-initialized semaphore?
The behavior is undefined. You must call sem_destroy() on an initialized semaphore before calling sem_init() again. Reinitializing without destroying first is a programming error.

Leave a Reply

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