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를 사용하는 것이 오히려 쉬울수 있음.
- 또 하나 중요한 점은 비동기와 멀티쓰레드는 다르다는 것
- 비동기 : 순차적으로 실행되진 않는다.
- 멀티쓰레드 : 동시에 여러작업이 가능하다.