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 |