반응형
Understanding Mutex and Semaphore – A Practical Example for Synchronization

Understanding Mutex and Semaphore – A Practical Example for Synchronization

Introduction

In multithreaded programming, synchronization is an essential concept. When multiple threads run concurrently and attempt to access shared resources, it can lead to unexpected outcomes or data inconsistency. To prevent these issues, synchronization mechanisms like Mutex and Semaphore are used.

This post will explore the differences between Mutex and Semaphore using a practical example, explaining how each method ensures safe resource access in a multithreaded environment.

Basic Concepts of Mutex and Semaphore

Mutex (Mutual Exclusion)

Mutex ensures mutual exclusion by allowing only a single thread to access a specific resource at a time. A thread must lock the Mutex to access the resource and unlock it when done to allow other threads access. Mutex is suitable for exclusive resource protection when only one thread should access a resource at any given time.

Semaphore

Semaphore operates based on an integer value to restrict access to resources. The initial value of a semaphore represents the maximum number of threads that can access the resource concurrently. When a thread accesses the resource, the semaphore's count decreases, and it increases again when the thread releases the resource. This allows for limited concurrent access to resources, making it useful when several threads need to access a resource simultaneously.

Practical Example: Solving Synchronization Issues with Mutex and Semaphore

In the example below, we use a Semaphore to allow up to three threads to access a resource simultaneously. We also use std::mutex to synchronize std::cout access, ensuring that each thread’s status messages are printed clearly.

Example Code


#include <iostream>
#include <thread>
#include <semaphore>  // Only available in C++20
#include <mutex>

std::counting_semaphore<3> sem(3); // Allows up to 3 threads to access simultaneously
std::mutex cout_mutex;

void accessResource(int id) {
    {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "Thread " << id << " is waiting to access the resource.\n";
    }

    sem.acquire();  // Permit access via semaphore

    {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "Thread " << id << " is accessing the resource.\n";
    }

    std::this_thread::sleep_for(std::chrono::seconds(1)); // Simulate resource usage

    {
        std::lock_guard<std::mutex> lock(cout_mutex);
        std::cout << "Thread " << id << " is releasing the resource.\n";
    }

    sem.release();  // Release semaphore
}

int main() {
    std::thread t1(accessResource, 1);
    std::thread t2(accessResource, 2);
    std::thread t3(accessResource, 3);
    std::thread t4(accessResource, 4);
    std::thread t5(accessResource, 5);

    t1.join();
    t2.join();
    t3.join();
    t4.join();
    t5.join();

    std::cout << "All threads have completed execution.\n";
    return 0;
}
    

Code Analysis

  • std::counting_semaphore<3> sem(3);: A semaphore that restricts simultaneous access to a maximum of three threads.
  • std::lock_guard<std::mutex> lock(cout_mutex);: Protects std::cout access with cout_mutex, ensuring that messages are printed in sequence and do not overlap.
  • sem.acquire() and sem.release(): acquire() is called when a thread accesses the resource, and release() is called when the thread finishes.

Result Analysis

When this program runs, each thread waits until it can access the resource based on the semaphore limits. Here’s an example of the expected output:


Thread 1 is waiting to access the resource.
Thread 2 is waiting to access the resource.
Thread 3 is waiting to access the resource.
Thread 1 is accessing the resource.
Thread 2 is accessing the resource.
Thread 3 is accessing the resource.
Thread 4 is waiting to access the resource.
Thread 5 is waiting to access the resource.
Thread 1 is releasing the resource.
Thread 4 is accessing the resource.
Thread 2 is releasing the resource.
Thread 5 is accessing the resource.
Thread 3 is releasing the resource.
All threads have completed execution.
    

As shown, the semaphore limits resource access to three threads at a time. Thread 1, Thread 2, and Thread 3 access the resource simultaneously, while Thread 4 and Thread 5 wait. When Thread 1 releases the resource, Thread 4 can access it, and so on, until all threads finish.

Difference Between Mutex and Semaphore

This example illustrates the main differences between Mutex and Semaphore.

  • Mutex guarantees exclusive access, allowing only one thread to access a resource at a time.
  • Semaphore enables a set number of threads to access a resource simultaneously, making it suitable for limited concurrent access.

Conclusion

In multithreaded programming, synchronization mechanisms are essential to prevent race conditions, where multiple threads simultaneously access shared resources. Mutex and Semaphore serve different synchronization purposes and should be chosen based on the specific access requirements of the program.

This example demonstrates that Mutex is useful when exclusive access is required, while Semaphore works well for limiting the number of threads that can access a resource concurrently. Using these two synchronization methods effectively helps ensure the stability and efficiency of multithreaded applications.

반응형

+ Recent posts