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

Modern C++ : std::shared_timed_mutex (14)

by snowoods 2025. 10. 19.

Modern C++

 

shared timed mutex

 

개요

std::shared_timed_mutex는 여러 스레드가 데이터를 읽는 것을 허용하면서, 데이터 쓰기는 단 하나의 스레드만 허용하는 읽기-쓰기 잠금(Read-Write Lock)을 구현합니다. 읽기 작업이 쓰기 작업보다 훨씬 빈번하게 발생하는 경우, 일반 std::mutex보다 높은 성능을 제공할 수 있습니다.

 

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

  • C++14: std::shared_timed_mutex가 표준에 추가되었습니다.
  • C++14: std::shared_lockstd::shared_timed_mutex와 함께 사용하기 위한 RAII 래퍼로 추가되었습니다.

 

내용 설명

std::shared_timed_mutex는 두 가지 종류의 잠금을 제공합니다.

  1. 공유 잠금 (Shared Lock): 여러 스레드가 동시에 획득할 수 있는 잠금입니다. 주로 데이터 읽기 작업에 사용됩니다. std::shared_lock을 통해 RAII 방식으로 안전하게 관리할 수 있습니다.
  2. 배타적 잠금 (Exclusive Lock): 단 하나의 스레드만 획득할 수 있는 잠금입니다. 주로 데이터 쓰기 작업에 사용됩니다. std::unique_lock 또는 std::lock_guard로 관리합니다.

쓰기 스레드가 배타적 잠금을 요청하면, 기존의 모든 읽기 스레드가 공유 잠금을 해제할 때까지 대기합니다. 반대로, 읽기 스레드가 공유 잠금을 사용 중일 때는 다른 읽기 스레드들은 바로 진입할 수 있지만, 쓰기 스레드는 대기해야 합니다.

 

예제 코드

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

class ThreadSafeCounter {
public:
    ThreadSafeCounter() = default;

    // 여러 스레드가 동시에 호출 가능 (읽기)
    unsigned int get() const {
        std::shared_lock<std::shared_timed_mutex> lock(mutex_);
        return value_;
    }

    // 한 번에 한 스레드만 호출 가능 (쓰기)
    void increment() {
        std::unique_lock<std::shared_timed_mutex> lock(mutex_);
        value_++;
    }

private:
    mutable std::shared_timed_mutex mutex_;
    unsigned int value_ = 0;
};

int main() {
    ThreadSafeCounter counter;

    auto writer = [&]() {
        for (int i = 0; i < 5; ++i) {
            counter.increment();
            std::cout << "Writer: incremented to " << counter.get() << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(500));
        }
    };

    auto reader = [&](int id) {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Reader " << id << ": sees value " << counter.get() << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(200));
        }
    };

    std::thread writer_thread(writer);
    std::vector<std::thread> reader_threads;
    for (int i = 0; i < 3; ++i) {
        reader_threads.emplace_back(reader, i + 1);
    }

    writer_thread.join();
    for (auto& t : reader_threads) {
        t.join();
    }

    return 0;
}

 

실행 결과

Reader 1: sees value 0
Reader 2: sees value 0
Reader 3: sees value 0
Writer: incremented to 1
Reader 1: sees value 1
Reader 2: sees value 1
Reader 3: sees value 1
Reader 1: sees value 1
Reader 2: sees value 1
Writer: incremented to 2
Reader 3: sees value 2
...

(실행 시점에 따라 순서는 달라질 수 있습니다.)

 

활용팁

  • 읽기 위주 환경에 최적: 공유 데이터에 대한 읽기 작업이 쓰기 작업보다 압도적으로 많을 때 사용하면 동시성을 높여 성능 이점을 얻을 수 있습니다.
  • std::shared_lock 활용: 읽기 잠금을 위해서는 RAII 패턴을 따르는 std::shared_lock을 사용하는 것이 안전하고 편리합니다.
  • 쓰기 기아(Writer Starvation) 주의: 읽기 스레드가 계속해서 잠금을 획득하는 경우, 쓰기 스레드가 잠금을 얻지 못하고 무한정 대기하는 '쓰기 기아' 상태가 발생할 수 있습니다. 시스템 설계 시 이를 고려해야 합니다.