C++ future(promise, async, packaged_task)
오늘은 C++의 future에 대해서 알아보겠습니다.
우리는 일반적으로 코드가 실행된다고 하면, 순차적으로 실행되는 것을 생각합니다.
1~10번까지의 코드가 있다면,
1번이 실행되고 2번이
2번이 실행되고 3번이 이렇게 말입니다.
그런데 만약 1번 명령문을 수행하는데에 아주 오랜 시간이 걸리면 어떻게 될까요?
2~10번코드는 1번 명령문이 완료되기를 기다리게 됩니다.
아주 오랜시간동안요.
하지만, 1번 명령문이 나머지 2~10번 코드에 영향을 미치는 코드가 아니라면?
1번이 반환하는 결과가 당장 필요하지 않다면?
그럴 때는 1번 명령을 어떤 스레드에게 맡겨놓고,
1번 명령이 완료되기 전에, 나머지 2~10번을 수행할 수 있을것입니다.
그리고, 이 때 1번 명령의 결과값을 저~기 7이나 8번즈음에서
받기 위해서 사용하는 방법이 future입니다.
future를 이용하기위해 제공되는 세 가지 방법 promise, async, packaged_task에 대해서 알아보겠습니다.
Promise
promise는 future에게 전달할 실행결과를 저장하는 것이라고 생각하면 됩니다.
코드를 먼저 보면 이해가 빠를 겁니다.
#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>
__int64 Calculate()
{
__int64 sum = 0;
for (int i = 0; i < 1000000; i++)
sum += i;
return sum;
}
void PromiseWork(std::promise<__int64>&& promise)
{
promise.set_value(Calculate());
}
void main()
{
//받아올 값을 저장할 promise를 생성!
std::promise<__int64> promise;
//promise에 저장'될' 값을 future를 통해 출력할 수 있다.
std::future<__int64> future = promise.get_future();
/*지금까지는 Calculate가 실행되지 않았지만, future를 통해서 그 결과를 받아오게 될것이다.*/
//Calculate를 실행하고, promise에 그 결과값을 셋팅한다.
//promise는 copy할 수 없고, move해야한다.
std::thread t(PromiseWork, std::move(promise));
//다른 작업ing...
//다른 스레드가 실행한 Calculate의 결과값을 여기서 받아온다.
//만약 실행이 끝나지 않았으면 block하고 끝날 때 까지 대기한다.
__int64 sum = future.get();
cout << sum << endl;
t.join();
}
- 참고사항
- future는 get을 한 번만 호출할 수 있다.
- promise는 set_value()를 한 번만 호출할 수 있다.
Async
번역하면, 비동기라는 의미인데
실핸순서가 선형이 아니라는 뜻입니다.
#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>
__int64 Calculate()
{
cout << "start" << endl;
__int64 sum = 0;
for (int i = 0; i < 10000000; i++)
sum += i;
cout << "ends" << endl;
return sum;
}
void main()
{
std::future<__int64> future = std::async(std::launch::async, Calculate);
//std::future<__int64> future = std::async(std::launch::deferred, Calculate);
//std::future<__int64> future = std::async(Calculate);
//다른 작업ing...
__int64 sum = future.get();
cout << sum << endl;
}
오히려 코드는 간단합니다.
async의 policy로 넘겨주는 인자에 대해서 알아보겠습니다.
- std::launch::async : 호출하는 순간 새로운 스레드를 하나 생성해서, 함수를 실행합니다.
- std::launch::deferred : future.get()을 하는 순간, 스레드를 생성하지 않고, 함수를 실행합니다.
- default : async | deferred : C++런타임이 알아서 결정합니다.
packaged_task
#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>
__int64 Calculate()
{
cout << "start" << endl;
__int64 sum = 0;
for (int i = 0; i < 10000000; i++)
sum += i;
cout << "ends" << endl;
return sum;
}
void TaskWorker(std::packaged_task<__int64(void)>&& task)
{
task();
}
void main()
{
//return type, parameter type을 넘겨준다.
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는 promise를 자동으로 생성하고, 호출한 함수의 결과를 promise에 저장해준다.
언제쓰는가?
대표적으로 예외처리할 때 스레드끼리 exception을 주고받을 때 활용할 수 있다.
#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>
int calculate()
{
throw runtime_error("Exception thrown from calculate()");
}
int main()
{
std::future<int> future = std::async(std::launch::async, calculate);
//다른 작업ing...
try{
int result = future.get();
cout << result << endl;
}
catch (exception e) {
cout << e.what() << endl;
}
}