오구의코딩모험

[Server] Condition Variable, Future 본문

Game/Server

[Server] Condition Variable, Future

오구.cpp 2025. 3. 28. 19:36
반응형

 

 

멀티스레드 동기화와 비동기 처리: condition_variable & future

 


서버 개발에서는 멀티스레드를 다룰 때 반드시 스레드 간 동기화비동기 처리에 대한 이해가 필요합니다.  
오늘은 'condition_variable'을 이용한 생산자-소비자 패턴 구현, 그리고 'future', 'promise', 'packaged_task'를 사용한 비동기 처리 방식에 대해 공부했습니다.

 


 



1. condition_variable

핵심 개념

- condition_variable은 한 스레드가 특정 조건을 충족했을 때 다른 스레드에게 알림을 보내는 동기화 도구입니다.  
- 일반적으로 mutex와 함께 사용하며, 생산자-소비자 구조에 적합합니다.

아래는 Producer 스레드가 데이터를 추가하고 Consumer 스레드가 이를 소비하는 구조입니다.

std::condition_variable cv;
std::mutex m;
std::queue<int32_t> q;

void Producer()
{
    while (true)
    {
        {
            std::unique_lock<std::mutex> lock(m);
            q.push(100);
        }

        cv.notify_one(); // 대기 중인 스레드 중 하나를 깨움
        std::this_thread::sleep_for(10000ms);
    }
}

void Consumer()
{
    while (true)
    {
        std::unique_lock<std::mutex> lock(m);
        cv.wait(lock, []() { return q.empty() == false; });

        int32_t data = q.front();
        q.pop();
        std::cout << data << std::endl;
    }
}

 


※ 주의사항 : Spurious Wakeup
조건을 만족하지 않았는데도 스레드가 깨어날 수 있습니다.

이를 Spurious Wakeup이라고 하며, wait()에 반드시 조건 함수(람다)를 포함해 방지해야 합니다.

 

 

 


2. std::future & async

int64_t Calculate()
{
    int64_t sum = 0;
    for (int32_t i = 0; i < 100'000; i++)
        sum += i;
    return sum;
}

std::future<int64_t> future = std::async(std::launch::async, Calculate);
std::future_status status = future.wait_for(1ms);
int64_t sum = future.get(); // 결과 필요 시 대기하고 받아오기

 

개념 요약

 - std::async을 사용하면 함수를 비동기적으로 실행할 수 있습니다.
 - 실행 결과는 future를 통해 나중에 받아올 수 있습니다.

 

3. std::promise

void PromiseWorker(std::promise<std::string>& promise)
{
promise.set_value("Secret Message");
}

std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();

std::thread t(PromiseWorker, std::move(promise));
std::string message = future.get();

t.join();


핵심 개념

 - promise는 값을 나중에 줄 것을 약속하고, future는 그 값을 기다리는 역할을 합니다.
 - 서로 다른 스레드 간 데이터 전달이 필요한 경우에 유용합니다.

 

4. std::packaged_task

void TaskWorker(std::packaged_task<int64_t(void)>&& task)
{
    task(); // 포장된 함수 실행
}

std::packaged_task<int64_t(void)> task(Calculate);
std::future<int64_t> future = task.get_future();

std::thread t(TaskWorker, std::move(task));
int64_t sum = future.get();

t.join();


개념 요약

- packaged_task는 특정 함수를 포장하여 나중에 실행하고, 실행 결과를 future로 받을 수 있게 합니다.

 


5. 정리 및 활용법

 

어떤 상황에 어떤 도구를 써야 할까?


*** condition_variable : 반복적으로 조건을 만족하는 경우에 다른 스레드에 알려주고 싶을 때

*** future + async : 단순히 비동기 함수 실행 + 결과 대기가 필요한 경우

*** promise : 한 스레드에서 값을 계산하여 다른 스레드로 전달할 때

*** packaged_task : 실행 시점을 컨트롤하면서 결과도 받아야 하는 경우

 

 



결론

 

이번 학습을 통해 스레드 간 통신과 데이터 처리 방식을 다양한 방법으로 처리할 수 있음을 배웠습니다.
특히 condition_variable은 고전적인 동기화 도구로 유용하고, future, promise, packaged_task는 좀 더 선언적이고 간편한 방식으로 스레드 간 결과 전달을 가능하게 합니다.

반응형
Comments