Semaphore Explained: Types, Operations, and Use Cases

You’re asking for a clear, beginner-friendly English explanation of Semaphore—a key synchronization tool for multi-threading that solves problems like resource limiting and thread coordination (building on our earlier discussions of threads, multi-threading, and deadlocks). I’ll break down its definition, types, use cases, and provide a practical code example to show how it works.

1. Core Definition & Simple Explanation

  • Term: Semaphore (Pronunciation: /ˈseməfɔːr/)
  • Simple Definition:A semaphore is a synchronization primitive (a tool for controlling thread access) that manages a count of available resources. It allows a fixed number of threads to access a shared resource (or section of code) at the same time—threads wait if the resource count is zero, and release the resource to increment the count when done.
  • Analogy for Beginners:Think of a semaphore as a bouncer at a nightclub with a maximum capacity (e.g., 50 people):
    • When the club is empty (count = 50), the bouncer lets people in (threads access the resource), decrementing the count each time.
    • When the club is full (count = 0), new people wait outside (threads block) until someone leaves (a thread releases the resource), incrementing the count again.

2. Key Concepts & Types of Semaphores

Core Operations

All semaphores rely on two atomic (uninterruptible) operations:

  • wait() (also called P()): Decrements the semaphore count. If the count becomes negative, the thread blocks (waits) until another thread calls post().
  • post() (also called V()): Increments the semaphore count. If threads are waiting, one is woken up to proceed.

Common Types

TypeExplanationUse Case
Counting SemaphoreHas a variable count (e.g., 5, 10) to limit access to N resources.Limiting concurrent database connections, thread pools.
Binary SemaphoreA special case with a count of 0 or 1 (acts like a mutex lock).Basic mutual exclusion (one thread at a time).

3. Practical Code Example (Counting Semaphore)

This example uses a counting semaphore to limit 3 concurrent threads from accessing a “shared resource” (simulated with a print task)—even if we create 5 threads, only 3 run at a time:

c

运行

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

// Define the maximum number of concurrent threads (semaphore count = 3)
#define MAX_CONCURRENT_THREADS 3
// Semaphore to control resource access
sem_t resource_semaphore;

// Function each thread runs (accesses the shared resource)
void* thread_worker(void* thread_id) {
    long tid = (long)thread_id;

    // Step 1: Wait for the semaphore (acquire resource)
    // If count > 0: decrement count and proceed
    // If count = 0: block until another thread calls sem_post()
    sem_wait(&resource_semaphore);
    printf("Thread %ld: Acquired resource (semaphore count reduced)\n", tid);

    // Simulate work with the shared resource (e.g., read/write a file, connect to DB)
    sleep(2);

    // Step 2: Post (release) the semaphore (free up resource)
    // Increment count and wake a waiting thread (if any)
    sem_post(&resource_semaphore);
    printf("Thread %ld: Released resource (semaphore count increased)\n", tid);

    pthread_exit(NULL);
}

int main() {
    const int TOTAL_THREADS = 5; // More threads than allowed concurrent access
    pthread_t threads[TOTAL_THREADS];
    long t;

    // Initialize the semaphore:
    // - First argument: semaphore variable
    // - Second argument: 0 = shared between threads of this process (1 = shared between processes)
    // - Third argument: initial count (MAX_CONCURRENT_THREADS = 3)
    if (sem_init(&resource_semaphore, 0, MAX_CONCURRENT_THREADS) != 0) {
        perror("Semaphore initialization failed");
        exit(EXIT_FAILURE);
    }

    // Create 5 threads (only 3 can run at a time)
    for (t = 0; t < TOTAL_THREADS; t++) {
        int ret = pthread_create(&threads[t], NULL, thread_worker, (void*)t);
        if (ret != 0) {
            perror("Failed to create thread");
            exit(EXIT_FAILURE);
        }
        // Small delay to show ordering clearly
        usleep(100000); // 0.1 seconds
    }

    // Wait for all threads to finish
    for (t = 0; t < TOTAL_THREADS; t++) {
        pthread_join(threads[t], NULL);
    }

    // Clean up the semaphore
    sem_destroy(&resource_semaphore);
    printf("\nAll threads completed. Semaphore cleaned up.\n");
    return 0;
}

Compile & Run (Linux/macOS):

bash

运行

# Compile (link with pthread for threads + semaphore support)
gcc semaphore_example.c -o semaphore_example -pthread

# Run
./semaphore_example

Expected Output (Controlled Concurrency):

plaintext

Thread 0: Acquired resource (semaphore count reduced)
Thread 1: Acquired resource (semaphore count reduced)
Thread 2: Acquired resource (semaphore count reduced)
# Threads 3 and 4 wait here (semaphore count = 0)
Thread 0: Released resource (semaphore count increased)
Thread 3: Acquired resource (semaphore count reduced)
Thread 1: Released resource (semaphore count increased)
Thread 4: Acquired resource (semaphore count reduced)
Thread 2: Released resource (semaphore count increased)
Thread 3: Released resource (semaphore count increased)
Thread 4: Released resource (semaphore count increased)

All threads completed. Semaphore cleaned up.

(Notice only 3 threads acquire the resource at first—threads 3 and 4 wait until others release it!)

4. Key Differences: Semaphore vs. Mutex

Beginners often confuse semaphores with mutexes—here’s a clear comparison:

AspectSemaphoreMutex
OwnershipNo ownership (any thread can call post())Owned by the thread that locked it (only the owner can unlock it).
CountVariable (0 to N)Binary (0 or 1)
Use CaseLimit concurrent access to N resourcesEnsure only one thread accesses a resource
FlexibilityCan coordinate multiple threadsSimple mutual exclusion

5. Real-World Use Cases for Semaphores

  • Resource Pooling: Limit concurrent database connections (e.g., only 10 threads can connect to a DB at once to avoid overwhelming it).
  • Thread Pools: Control the number of worker threads in a pool (e.g., a web server with 20 worker threads to handle requests).
  • Producer-Consumer Problems: Coordinate producers (adding data to a queue) and consumers (removing data) to avoid empty/full queue issues.
  • Hardware Limitations: Restrict access to physical hardware (e.g., only 2 threads can use a GPU at once).

Summary

Unlike mutexes, semaphores have no ownership—any thread can release a semaphore, making them flexible for resource limiting (not just mutual exclusion).

Semaphore is a synchronization tool that manages a count of available resources, allowing a fixed number of threads to access a resource concurrently via sem_wait() (acquire) and sem_post() (release).

Counting semaphores limit N concurrent threads (e.g., 3 database connections), while binary semaphores act like mutexes (1 thread at a time).



了解 Ruigu Electronic 的更多信息

订阅后即可通过电子邮件收到最新文章。

Posted in

Leave a comment