Future

동기 vs 비동기

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <future>

int64 Calculate()
{
    int64 sum = 0;
    
    for (int32 i = 0 ; i < 100'000 ; i++)
        sum += i;
    
    result = sum;
    
    return sum;
}

int main()
{
    // 동기(synchronous) 실행
    int64 sum = Calculate();
    
    cout << sum << endl;
    
    return 0;
}
  • 위 코드는 동기적이다.
  • 만약 Calculate가 굉장히 오랜 시간동안 연산하고, 그것을 기다리는 동안 다른 작업을 하고 싶다면?
  • 우리가 배운 내용을 토대로 아래와 같이 작성 가능하다.

Thread 이용

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <future>

int64 result;

int64 Calculate()
{
    int64 sum = 0;
    
    for (int32 i = 0 ; i < 100'000 ; i++)
        sum += i;
    
    result = sum;
    
    return sum;
}

int main()
{
    thread t(Caltulate);
    
    //다른 작업 수행
    
    t.join();
    
    cout << result << endl;
    
    return 0;
}
  • thread를 생성 후 Calculate() task를 위임하여 비동기적으로 작동한다.=
  • 간단한 작업을 하는데도 불구하고, 굳이 쓰레드까지 만들어서 관리하기 귀찮다.

-> std::future & std::async 를 통해 해결 가능하다.

future + async

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <future>

int64 Calculate()
{
    int64 sum = 0;
    
    for (int32 i = 0 ; i < 100'000 ; i++)
        sum += i;
    
    result = sum;
    
    return sum;
}

int main()
{
    // std::future
    {
        std::future<int64> future = std::async(std::launch::async/*정책*/, Calculate);
    
        // 다른 작업 수행
        
        //10ms 동안 future 상태 관찰
        std::future_status status = future.wait_for(10ms);
        
        int64 sum = future.get(); //결과물이 이제서야 필요하다!
    }
}

async에는 두가지 policy가 있다.

enum class launch {
  async    = 0x1,
  deferred = 0x2,
}

Options

  1. deferred
    • lazy evaluation : 지연해서 실행하세요 -> 테스트 결과 future.get()을 호출할 때가 돼서야 쓰레드로 분기되는듯?
  2. async
    • 별도의 쓰레드를 만들어서 실행하세요
  3. deferred | async
    • 둘 중 알아서 골라주세요

future + promise

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <future>

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

void PromiseWorker(std::promise<string>&& promise)
{
    promise.set_value("Secret Message");
}
int main()
{
    // std::promise
    {
         // 미래(std::future)에 결과물을 반환해줄거라 약속
         std::promise<string> promise;
         std::future<string> future = promise.get_future();
         
         thread t(PromiseWorker, std::move(promise));
         
         string message = future.get();
         cout << message << endl;
         
         t.join();
    }
}

🏳️이 부분은 아직 모던 C++에 대해 배우지 않아 대충 이해만 하고 넘어갔다.
모던 C++ 학습 후 오른값 참조에 대해 복습해야할 것

future + packaged_task

#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <future>

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

void TaskWorker(std::packaged_task<int64>&& task)
{
    task(); //함수를 호출
}

int main()
{
    // std::packaged_task
    {
         std::packaged_task<int64(void)> task(Calculate);
         std::future<int64> future = task.get_future();
         
         std::thread t(TaskWorker, std::move(task));
         
         int64 sum = future.get();
         cout << sum << endl;
         
         t.join();
    }
}
  • packaged_task의 타입은 함수타입과 맞춰줘야 한다. -> Calculate() 이기 때문에 int64, void
  • promise - future 의 관계와 packaged_task - future 의 관계는 비슷하다.

결론

  • mutex, condition_variable을 사용하지 않고 단순한 기능을 처리할 수 있는 방법에 대해 알아보았다.
  • 1회성 이벤트에 유용하다.
  1. async : 원하는 함수를 비동기적으로 실행
  2. promise : 결과물을 promise를 통해 future로 받아줌
  3. packaged_task : 원하는 함수의 실행 결과를 packaged_task를 통해 future로 받아줌