반응형
C++17에서의 mutex, lock
1. 기본 std::mutex 사용
std::mutex는 임계 구역(critical section)을 보호하기 위한 가장 기본적인 뮤텍스입니다.
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
void print_message(const std::string& msg) {
mtx.lock();
std::cout << msg << std::endl;
mtx.unlock();
}
int main() {
std::thread t1(print_message, "Hello from thread 1");
std::thread t2(print_message, "Hello from thread 2");
t1.join();
t2.join();
return 0;
}
주의: lock()과 unlock()은 반드시 쌍으로 호출되어야 하며, 예외 발생 시 unlock이 호출되지 않아 데드락의 원인이 될 수 있음.
2. std::lock_guard 사용
std::lock_guard는 예외 안전성을 위해 사용되는 RAII 스타일의 뮤텍스 관리 도구입니다.
void print_message(const std::string& msg) {
std::lock_guard<std::mutex> lock(mtx);
std::cout << msg << std::endl;
}
스코프를 벗어나면 자동으로 unlock()이 호출됨.
3. std::unique_lock 사용
std::unique_lock은 lock_guard보다 더 유연한 뮤텍스 관리가 가능합니다. 예: 지연 잠금, 조건 변수와의 조합 등
void print_message(const std::string& msg) {
std::unique_lock<std::mutex> lock(mtx);
std::cout << msg << std::endl;
// lock.unlock(); // 수동 해제도 가능
}
4. std::recursive_mutex
같은 쓰레드에서 여러 번 lock이 필요한 경우 사용합니다.
std::recursive_mutex rmtx;
void recursive_function(int count) {
if (count <= 0) return;
rmtx.lock();
std::cout << "Depth: " << count << std::endl;
recursive_function(count - 1);
rmtx.unlock();
}
5. std::timed_mutex, std::recursive_timed_mutex
특정 시간 내에 lock을 얻지 못하면 포기하도록 설계된 뮤텍스입니다.
std::timed_mutex tmtx;
void try_lock_example() {
if (tmtx.try_lock_for(std::chrono::milliseconds(100))) {
std::cout << "Lock acquired" << std::endl;
tmtx.unlock();
} else {
std::cout << "Timeout: failed to acquire lock" << std::endl;
}
}
6. std::lock 함수
여러 뮤텍스를 동시에 안전하게 lock할 때 사용합니다. 데드락 방지를 위한 도구입니다.
데드락 케이스
// 쓰레드 A
m1.lock();
m2.lock(); // 잠금 대기 중
// 쓰레드 B
m2.lock();
m1.lock(); // 잠금 대기 중 → 데드락 발생!
쓰레드는 제 각각 돌기 때문에, 쓰레드A, B가 m1, m2를 lock을 한 다음 lock을 하게 되는 경우가 발생하게 되면 서로가 가진 뮤텍스를 기다리다가 영원히 대기 상태에 빠지게 됩니다.
#include <iostream>
#include <mutex>
#include <thread>
std::mutex m1, m2;
void threadA() {
std::lock(m1, m2); // 안전하게 두 뮤텍스를 모두 잠금
std::lock_guard<std::mutex> lk1(m1, std::adopt_lock);
std::lock_guard<std::mutex> lk2(m2, std::adopt_lock);
std::cout << "Thread A acquired both locks\n";
}
void threadB() {
std::lock(m2, m1); // 순서를 바꿔도 안전하게 잠금
std::lock_guard<std::mutex> lk1(m2, std::adopt_lock);
std::lock_guard<std::mutex> lk2(m1, std::adopt_lock);
std::cout << "Thread B acquired both locks\n";
}
int main() {
std::thread t1(threadA);
std::thread t2(threadB);
t1.join();
t2.join();
}
여기서 std::lock은 내부적으로 양쪽 쓰레드에서 같은 방식으로 동기화를 해주기 때문에 데드락 없이 둘 다 잠금에 성공할 수 있어요.
7. 주의사항 요약
- 뮤텍스는 가능한 한 짧은 영역에서만 잠그고 풀어야 함
- lock을 해제하지 않고 예외가 발생하면 데드락이 발생할 수 있음 → RAII 스타일 사용 권장
- 여러 뮤텍스를 동시에 잠글 경우 반드시 std::lock 사용
- 가능한 경우 std::scoped_lock (C++17부터 도입)을 사용하면 더 안전하게 여러 뮤텍스를 다룰 수 있음
std::mutex m1, m2;
void example() {
std::scoped_lock lock(m1, m2); // C++17: 데드락 방지 + RAII
// 임계 구역
}
'Develop > C&CPP' 카테고리의 다른 글
[cpp] thread (0) | 2025.04.20 |
---|---|
[cpp] atomic (0) | 2025.04.20 |
[cpp] cpp17에서 달라진 점 (0) | 2025.04.20 |
[cpp] cpp14에서 추가된 것 (0) | 2025.04.20 |
[service] 윈도우 서비스 프로그램 (0) | 2025.01.05 |