동기화 기법: Mutex

김 무무 ㅣ 2024. 10. 5. 19:42

1. Mutex란

Mutex(Mutual Exclusion)는 여러 스레드가 공유 리소스에 동시에 접근하는 것을 방지하는 동기화 기법이다.

Mutex를 사용하면 한 번에 하나의 스레드만 임계 영역에 진입할 수 있다.

 

특징:

1) 상호 배제: 한 스레드가 뮤텍스를 소유하고 있으면 다른 스레드는 대기해야 함

2) 소유권: 뮤텍스를 획득한 스레드만 해제할 수 있음

3) 임계 영역 보호: 공유 리소스에 대한 동시 접근을 막아 데이터 일관성 유지

 

 

2. Mutex 사용법

C++11부터 <mutex> 헤더를 통해 mutex 클래스를 제공한다.

 

ex)

#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int num = 0;

void Increment() {
    for (int i = 0; i < 1000000; i++) {
        mtx.lock();
        num++;
        mtx.unlock();
    }
}

int main() {
    std::thread t1(Increment);
    std::thread t2(Increment);

    t1.join();
    t2.join();

    std::cout << "값: " << num << std::endl;
}

 

 

RAII(Resource Acquisition Is Initialization) 원칙을 따르는 `std::lock_guard` 사용:

#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int num = 0;

void Increment() {
    for (int i = 0; i < 1000000; i++) {
        std::lock_guard<std::mutex> lock(mtx);
        num++;
    } // lock_guard의 소멸자에서 자동으로 unlock
}

int main() {
    std::thread t1(Increment);
    std::thread t2(Increment);

    t1.join();
    t2.join();

    std::cout << "값: " << num << std::endl;
}

 

 

3. Mutex 사용 시 주의사항

1) 데드락(Deadlock) 방지:

   - 여러 Mutex를 사용할 때는 항상 같은 순서로 획득하고 해제해야 함

   - `std::lock()`이나 `std::scoped_lock`을 사용하여 여러 뮤텍스를 동시에 잠글 수 있음

 

2) 과도한 lock 피하기:

   - lock의 범위를 최소화하여 성능 저하 방지

   - 필요한 부분만 lock으로 보호

 

3) 재귀적 Mutex 사용 주의:

   - `std::recursive_mutex`를 사용하면 같은 스레드에서 여러 번 잠글 수 있음

   - 하지만 코드 복잡성을 증가시키고 성능 저하를 일으킬 수 있음

 

4) 예외 처리:

   - `std::lock_guard`나 `std::unique_lock`을 사용하여 예외 발생 시에도 뮤텍스가 해제되도록 함

 

5) 우선순위 역전 문제:

   - 우선순위가 높은 스레드가 우선순위가 낮은 스레드가 소유한 뮤텍스를 기다리는 상황

   - 가능하면 우선순위 상속(priority inheritance) 프로토콜을 지원하는 뮤텍스 사용

 

 

4. Mutex vs Lock

Mutex: 상호 배제를 위한 동기화 객체

Lock: 뮤텍스를 사용하는 방법을 추상화한 객체 (`std::lock_guard`, `std::unique_lock` 등)

 

ex)

#include <iostream>
#include <mutex>
#include <thread>
#include <chrono>
std::mutex mtx;
int num = 0;

void Increment() {
    std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 지연된 잠금
    while (!lock.try_lock()) {
        std::this_thread::sleep_for(std::chrono::milliseconds(50)); // 50밀리초 대기 후 재시도
    }
    num++;
}

int main() {
    std::thread t1(Increment);
    std::thread t2(Increment);

    t1.join();
    t2.join();

    std::cout << "값: " << num << std::endl;
}

 

위 예시처럼 unique_lock을 미리 생성해둔 후 원하는 타이밍에 잠금을 시도할 수 있다.

'멀티스레딩' 카테고리의 다른 글

스핀락(Spinlock)  (0) 2024.10.12
데드락(Deadlock)  (1) 2024.10.07
동기화 기법: Lock  (0) 2024.10.05
동기화 기법: Atomic 연산  (0) 2024.10.05
메모리 구조와 멀티스레딩  (0) 2024.10.03