Mutex는 멀티스레드 환경에서 공유 자원에 대한 접근을 제어하는 동기화 객체다.
여러 개의 스레드가 동시에 같은 자원에 접근할 때 발생할 수 있는 Race Condition을 방지하기 위해 사용된다.
코드 예시
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
mtx.lock();
counter++; // Critical Section
mtx.unlock();
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
주요 메서드
lock() unlock()try_lock()위 예시 코드처럼 lock()과 unlock() 사이의 코드 영역(Critical Section)는 한 번에 하나의 스레드만 실행할 수 있다.
하지만 unlock() 호출 전에 예외가 발생하거나 조기 반환하면 데드락이 발생한다.
특수한 상황을 위한 Mutex
recursive_mutexunlock()을 호출해야 완전히 해제된다.timed_mutextry_lock_for()와 try_lock_until() 메서드를 제공한다.std::timed_mutex tmtx;
void function() {
if (tmtx.try_lock_for(std::chrono::seconds(1))) {
// 1초 이내에 잠금 획득 성공
// Critical Section
tmtx.unlock();
} else {
// 타임아웃 발생
}
}
shared_mutex수동으로 lock()과 unlock()을 관리하는 것은 위험하다. 이 문제를 해결하는 방법이 RAII(Resource Acquisition Is Initialization) 패턴이다. 객체의 생성자에서 자원을 획득하고, 소멸자에서 자원을 해제하는 방식으로, C++의 스택 해제(stack unwinding) 메커니즘을 활용하여 예외가 발생하더라도 자원이 안전하게 해제되도록 보장한다.
lock guard는 C++ 표준 라이브러리에서 제공하는 클래스중 하나로 mutex 관리에 대한 실수를 줄일 수 있도록 해준다.
lock guard를 사용하면 mutex를 자동으로 잠그고 해당 범위가 벗어나면 lock_guard의 소멸자가 호출되어 자동으로 mutex를 해제한다.
#include <mutex>
#include <thread>
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
std::lock_guard<std::mutex> lock(mtx); // 생성 시 lock
counter++;
// 스코프를 벗어나면 자동으로 unlock
}
}
void safe_function() {
std::lock_guard<std::mutex> lock(mtx);
if (some_condition)
return;
process_data(); // 예외 발생해도 unlock 보장
}
lock()/unlock() 호출 가능하다.condition_variable과 같은 조건 변수와 함께 사용이 필수적이다.std::mutex mtx;
void flexible_function() {
std::unique_lock<std::mutex> lock(mtx, std::defer_lock); // 지연 잠금
prepare_data();
lock.lock(); // 필요한 시점에 수동으로 잠금
modify_shared_data();
lock.unlock(); // 수동으로 해제 가능
cleanup(); // 잠금 없이 다른 작업
// 스코프 종료 시 잠금 상태면 자동으로 unlock
}
// 조건 변수와의 사용 (가장 흔한 케이스)
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void wait_for_signal() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; }); // unique_lock 필수
process_data();
}
std::lock() 알고리즘을 사용한다.lock_guard와 동일하다.std::mutex mtx1, mtx2;
// 데드락 발생 가능한 코드
void thread1() {
std::lock_guard<std::mutex> lock1(mtx1);
std::lock_guard<std::mutex> lock2(mtx2); // 순서 문제
// ...
}
void thread2() {
std::lock_guard<std::mutex> lock2(mtx2);
std::lock_guard<std::mutex> lock1(mtx1); // 반대 순서!
// ...
}
// scoped_lock으로 데드락 방지
void safe_thread1() {
std::scoped_lock lock(mtx1, mtx2); // 데드락 회피 알고리즘 사용
// ...
}
void safe_thread2() {
std::scoped_lock lock(mtx2, mtx1); // 순서 상관없이 안전
// ...
}
Lock Guard는 RAII 패턴을 활용하여 mutex를 안전하게 관리하는 방법이다.
예외 발생이나 조기 반환 상황에서도 자동으로 잠금이 해제되므로, 수동으로 lock()/unlock()을 관리하는 것보다 훨씬 안전하다.
대부분의 경우 lock_guard로 충분하며, 특수한 상황에서만 unique_lock이나 scoped_lock을 사용하면 된다.
이들을 적절히 활용하면 멀티스레드 프로그래밍에서 발생할 수 있는 많은 버그를 예방할 수 있다.