(C++ : IOCP-4) Future

Posted by : at

Category : Cpp   iocp



Future는 언제 쓸까? -> 단발성 이벤트의 처리 방안
어떻게 보면 쓰임세가 적긴함(가볍게 보자)

#include <future>

// Calculate()라는 연산량이 많은 함수가 있다.
int64 Calculate()
{
    int64 sum = 0;

    for(int32 i = 0; i < 100'000; i++)
        sum += i;

    return sum;
}

int main()
{
    // 동기(Synchrononous) 실행 방식이라한다.
    Calculate();    // 이 함수를 모두 끝나고 다음줄이 실행됨.

    // 단, Calculate이 엄청나게 오래걸리는 함수라면? -> 비동기 방식의 호출이 필요해 진다.
}
thread t(Calculate);
t.join();               // 이렇게 하면될까?

// 문제1. sum을 받기위해서 공용데이터(thread를 나누면 return으로 데이터를 받을 길이 없음)를 써야하며 안정성에 의문이 든다.
// 문제2. 단순 함수호출을 위해 thread를 생성하는 것이 정말 옳은일인가 싶다

좀 더 가볍게 처리해보자. -> 이게 사용의 핵심이라 봐도 무방

std::fucture<int64> future = std::async(std::launch::async, Calculate); // 여길호출하면, Calculate이 끝날때까지 여기서 정지해 있지 않고 다음줄로 넘어간다.

// do something ...

int64 sum = future.get();   // 결과물은 이렇게 받을 수 있음.
  • future의 옵션
    • deferred -> 지연해서 실행(get을 호출시 실행, 지연실행일 뿐이지 multi thread가 아님)
    • async -> 별도의 쓰레드를 만들어 실행(실질적 multi thread)
    • deferred | async -> 둘 중 알아서 컴파일러가 결정해 주세요
std::future_status status = future.wait_for(1ms);
if(status == future_status::ready)  // 완료되었나 확인이 가능
{

}

// ...

future.wait();  // 완료되기를 대기할 수도 있다.

주의할 점은 특정 객체의 함수를 Future로 호출시

class Myclass
{
public:
    int64 GetHp() {/*do something*/}
};

Myclass mc;
std::future<int64> future = std::async(std::launch::async, &Myclass::GetHp, mc);

promise

두 번째 future사용법
std::promise를 이용해 thread의 리턴값 쉽게 받기

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

// ...

// 미래에 결과물을 반환해줄꺼라 약속
// promise에 데이터가 set되면 미래에 get으로 데이터를 받을 수 있다.
std::promise<string> promise;
std::future<string> future = promise.get_future();

thread t(PromiseWorker, std::move(promise));    // move를 통해 다른 thread에 권한을 이전
string message = future.get();                  // future도 get을 호출시 삭제된다.(딱 한 번만 쓸 수 있음)
// 여기서 get이 된다는 말 자체가 set_value가 끝났다는 말임.
t.join();

packaged_task

위에서 사용한 promise를 thread를 생성했는데 그거까지도 하기 싫다면?

void TaskWorker(std::packaged_task<int64(void)>&& task)
{
    task();
    // 리턴이 없는데 future로 리턴된 데이터를 받을 수 있다는게 특징
}

//...

// std::packaged_task<함수아웃풋(함수인풋)>
std::packaged_task<int64(void)> task(Calculate);
    // 이러면 async로 thread를 만드는 것과 동일하지 않나?
    // packaged_task는 개발자 자신이 thread를 만드는데 하나의 thread를 생성 후 여러 packaged_task를 처리하게 할 수도 있다.
    // 하나의 Thread에 일감(packaged_task)를 넘길 수 있으니 Thread를 만들었다 지웠다 할 필요가 없음
std::future<int64> future = task.get_future();

std::thread t(TaskWorker, std::move(task));
int64 sum = future.get();
t.join();
  • 결론
    • mutex, condition_variable까지 가지않고 간단하게 thread를 사용해보자.
    • 단발성 이벤트의 경우 Future를 사용하는 것이 오히려 쉬울수 있음.
  • 또 하나 중요한 점은 비동기와 멀티쓰레드는 다르다는 것
  • 비동기 : 순차적으로 실행되진 않는다.
  • 멀티쓰레드 : 동시에 여러작업이 가능하다.

About Taehyung Kim

안녕하세요? 8년차 현업 C++ 개발자 김태형이라고 합니다. 😁 C/C++을 사랑하며 다양한 사람과의 협업을 즐깁니다. ☕ 꾸준한 자기개발을 미덕이라 생각하며 노력중이며, 제가 얻은 지식을 홈페이지에 정리 중입니다. 좀 더 상세한 제 이력서 혹은 Private 프로젝트 접근 권한을 원하신다면 메일주세요. 😎

Star
Useful Links