본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : std::scoped_lock (17)

by snowoods 2025. 10. 20.

Modern C++

 

scoped lock

 

개요

std::scoped_lock은 C++17에 도입된 RAII 스타일의 뮤텍스 래퍼(wrapper)로, 하나 이상의 뮤텍스를 교착 상태(deadlock)의 위험 없이 안전하게 잠그기 위해 사용됩니다. 생성자에서 전달된 모든 뮤텍스를 잠그고, 소멸자에서 자동으로 해제하여 사용이 간편하고 안전합니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++17: std::scoped_lock이 표준에 추가되었습니다.

 

내용 설명

여러 개의 뮤텍스를 다룰 때, 스레드마다 뮤텍스를 잠그는 순서가 다르면 교착 상태가 발생할 수 있습니다. 예를 들어, 스레드 1은 mutexA를 잠근 후 mutexB를 잠그려 하고, 스레드 2는 mutexB를 잠근 후 mutexA를 잠그려 하면 두 스레드 모두 영원히 기다리는 상태에 빠질 수 있습니다.

std::scoped_lock은 내부적으로 교착 상태 방지 알고리즘(deadlock-avoidance algorithm)을 사용하여 인자로 전달된 모든 뮤텍스를 안전한 순서로 잠급니다. 이로 인해 개발자는 잠금 순서를 신경 쓸 필요 없이 여러 뮤텍스를 한 번에 잠글 수 있습니다.

이는 C++11의 std::lock() 함수와 std::lock_guard(mutex, std::adopt_lock)를 함께 사용하던 복잡한 패턴을 대체하는 현대적이고 간결한 방법입니다.

 

예제 코드

#include <iostream>
#include <thread>
#include <mutex>
#include <vector>
#include <chrono>

struct Account {
    double balance;
    std::mutex m;
};

void transfer(Account& from, Account& to, double amount) {
    // std::scoped_lock을 사용하여 두 뮤텍스를 교착 상태 없이 잠급니다.
    std::scoped_lock lock(from.m, to.m);

    if (from.balance >= amount) {
        from.balance -= amount;
        to.balance += amount;
        std::cout << "Transfer successful: " << amount << " from " << &from << " to " << &to << std::endl;
    } else {
        std::cout << "Transfer failed: Insufficient funds" << std::endl;
    }
} // lock이 스코프를 벗어나면서 from.m과 to.m이 자동으로 해제됩니다.

int main() {
    Account acc1{100.0};
    Account acc2{50.0};

    std::cout << "Initial balances: acc1=" << acc1.balance << ", acc2=" << acc2.balance << std::endl;

    // 두 스레드가 서로 반대 방향으로 송금 (교착 상태 발생 가능성이 있는 시나리오)
    std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 20.0);
    std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 10.0);

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

    std::cout << "Final balances:   acc1=" << acc1.balance << ", acc2=" << acc2.balance << std::endl;

    return 0;
}

 

실행 결과

Initial balances: acc1=100, acc2=50
Transfer successful: 20 from 0x... to 0x...
Transfer successful: 10 from 0x... to 0x...
Final balances:   acc1=90, acc2=60

(실행 시점에 따라 송금 성공 메시지의 순서는 바뀔 수 있습니다.)

 

활용팁

  • 두 개 이상의 뮤텍스 잠금: 두 개 이상의 뮤텍스를 동시에 잠가야 할 때 가장 먼저 고려해야 할 방법입니다.
  • 가변 인자 템플릿: std::scoped_lock은 가변 인자 템플릿으로 구현되어 있어, 필요한 만큼의 뮤텍스를 인자로 전달할 수 있습니다. (std::scoped_lock lock(m1, m2, m3, ...);)
  • std::mutex 외 타입 지원: std::mutex뿐만 아니라 std::timed_mutex, std::shared_timed_mutexLockable 요구사항을 만족하는 모든 타입을 지원합니다.