반응형

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

+ Recent posts