Mutex와 Semaphore의 차이점 – 동기화 문제를 해결하는 실제 예제를 통해 이해하기
서론
멀티스레드 프로그래밍에서 동기화(synchronization)는 필수적인 개념입니다. 여러 스레드가 동시에 실행되는 상황에서 자원에 대한 접근을 조정하지 않으면, 예상치 못한 결과나 데이터 불일치가 발생할 수 있습니다. 이러한 문제를 방지하기 위해 Mutex(뮤텍스)와 Semaphore(세마포어) 같은 동기화 기법이 사용됩니다.
이 글에서는 Mutex와 Semaphore의 차이점을 실제 예제와 함께 살펴보고, 각 기법이 어떻게 멀티스레드 환경에서 자원 접근을 안전하게 관리할 수 있는지 자세히 설명하겠습니다.
Mutex와 Semaphore의 기본 개념
Mutex (Mutual Exclusion)
Mutex는 상호 배제를 통해 단일 스레드만 특정 자원에 접근할 수 있도록 제한합니다. 자원에 접근하려는 스레드는 Mutex를 잠그고(lock), 작업이 끝난 후 이를 해제(unlock)하여 다른 스레드가 자원에 접근할 수 있게 합니다. Mutex는 자원에 대한 배타적 접근을 보장해주기 때문에 단일 자원 보호에 적합합니다.
Semaphore
Semaphore는 정수 값을 기반으로 동작하며, 자원의 접근을 제한하는 용도로 사용됩니다. 세마포어의 초기값은 자원에 동시에 접근할 수 있는 스레드 수를 나타내며, 하나의 스레드가 자원에 접근할 때마다 세마포어 값이 감소하고, 자원 해제 시 다시 증가합니다. 이로써 여러 스레드가 자원에 동시에 접근할 수 있는 동시 접근 제한 기능을 제공하게 됩니다.
실전 예제: Mutex와 Semaphore를 통한 동기화 문제 해결
아래 예제에서는 Semaphore를 사용하여 최대 3개의 스레드가 동시에 자원에 접근할 수 있도록 설정합니다. 이를 통해 각 스레드가 자원을 사용하고 해제하는 과정을 확인할 수 있습니다. 또한, std::mutex를 사용하여 std::cout에 대한 접근을 동기화함으로써 각 스레드가 자원에 접근할 때의 상태를 명확히 볼 수 있습니다.
예제 코드
#include <iostream>
#include <thread>
#include <semaphore> // C++20에서만 사용 가능
#include <mutex>
std::counting_semaphore<3> sem(3); // 최대 3개의 스레드가 동시에 접근 가능
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(); // 세마포어를 통해 접근 허용
{
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)); // 자원 사용 시간
{
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "Thread " << id << " is releasing the resource.\n";
}
sem.release(); // 세마포어 해제
}
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;
}
코드 분석
std::counting_semaphore<3> sem(3);: 최대 3개의 스레드가 동시에 자원에 접근할 수 있도록 제한하는 세마포어입니다.std::lock_guard<std::mutex> lock(cout_mutex);:std::cout을 사용할 때마다cout_mutex를 사용하여 출력이 섞이지 않도록 보호합니다. 이를 통해 각 스레드의 상태 메시지가 순차적으로 출력됩니다.sem.acquire()와sem.release(): 스레드가 자원에 접근할 때acquire()로 자원을 차지하고, 작업이 완료되면release()로 해제합니다.
결과 분석
예제 프로그램을 실행하면 각 스레드는 자원에 접근하기 위해 대기 상태에 있다가, 세마포어가 허용하는 범위 내에서 자원에 접근하게 됩니다. 다음은 프로그램의 예상 출력 결과입니다.
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.
이 출력에서 확인할 수 있는 것처럼, 세마포어는 최대 3개의 스레드만 자원에 접근할 수 있게 관리하고 있습니다. Thread 1, Thread 2, Thread 3이 자원에 접근한 동안 Thread 4와 Thread 5는 대기하고 있습니다. Thread 1이 자원을 해제하면 대기 중이던 Thread 4가 접근하고, 이후 다른 스레드도 순차적으로 자원을 사용할 수 있게 됩니다.
Mutex와 Semaphore의 차이점
이 예제를 통해 Mutex와 Semaphore의 주요 차이점을 이해할 수 있습니다.
- Mutex는 자원을 오직 하나의 스레드만 사용할 수 있도록 보장하여, 한 번에 한 스레드만 접근해야 하는 자원을 보호합니다.
- Semaphore는 설정한 수만큼의 스레드가 동시에 자원에 접근할 수 있도록 허용하므로, 제한된 동시 접근이 필요할 때 유용합니다.
결론
멀티스레드 프로그래밍에서는 여러 스레드가 동시에 자원에 접근할 때 발생할 수 있는 경쟁 상태(Race Condition)를 방지하기 위해 동기화 기법이 필수적입니다. 이 글에서 설명한 Mutex와 Semaphore는 각기 다른 동기화 목적을 지니며, 상황에 맞게 적절히 선택하여 사용할 수 있습니다.
이번 예제를 통해, 자원의 독점적인 접근이 필요한 경우에는 Mutex를, 동시 접근 수를 제한해야 하는 경우에는 Semaphore를 사용할 수 있음을 이해할 수 있습니다. 이 두 동기화 기법을 잘 활용하여 멀티스레드 프로그램의 안정성과 효율성을 확보할 수 있을 것입니다.
'IT' 카테고리의 다른 글
| Introduction to Multithreading (1) | 2024.11.04 |
|---|---|
| Understanding Mutex and Semaphore – A Practical Example for Synchronization (2) | 2024.11.04 |
| SystemC Non-Blocking code (2) | 2024.09.06 |
| SystemC blocking code (1) | 2024.09.06 |
| Cache Modelling & Simulator (0) | 2024.08.31 |