기존에 함수를 실행해 결과를 받아오는 방법은 보통 '동기적'으로 결과를 가져온다고 표현하는데 아래의 간단한 예제를 보자.
int64 Calculate() {
int64 sum = 0;
for (int32 i = 0; i < 100'000; i++)
sum += i;
return sum;
}
int main() {
int64 sum = Calculate();
cout << sum << endl;
return 0;
}
위 코드의 결과는 간단한데 그저 sum에 Calculate의 결과가 입력되어 그 값을 출력하는 것이전부인 코드다. 하지만, Calculate를 실행하는 사이에 다른 작업을 수행하도록 하고 싶다면? 이를 할 수 있는 방법중 하나로 쓰레드를 사용하는 방법이 있는데 쓰레드를 사용하는 간단한 방법은 아래코드와 같다.
int64 Calculate() {
int64 sum = 0;
for (int32 i = 0; i < 100'000; i++)
sum += i;
return sum;
}
int main() {
thread t(Calculate);
// TODO
// 1. 공유데이터를 만들기에는 관리적인 측면에서 문제가 발생할수도
// 2. 단순한 일만 시킬것인데 하나의 쓰레드를 생성하는 것은 무거운 작업일 수도
t.join();
return 0;
}
쓰레드를 사용하면 join전까지 별도의 작업을 수행할 수 있지만, 쓰레드를 위한 공유 데이터나 쓰레드를 생성, 소멸하는데에 드는 비용이 비효율적일 수도 있다.
문자 그대로 미래객체 특정 연산이 완료될 때까지 기다릴 필요 없이 다른 작업을 계속할 수 있게 해준다. 즉, 비동기작업을 수행할 수 있는 방법 중 하나로 future를 사용할 수 있다.
아래 예제는 future를 이용해 기존의 Calculate의 값을 가져오는 코드이다.
int64 Calculate() {
int64 sum = 0;
for (int32 i = 0; i < 100'000; i++)
sum += i;
return sum;
}
int main() {
{
std::future<int64> future = std::async(std::launch::async, Calculate);
// 중간에 작업 가능
// 실제 값을 가져옴
int64 sum = future.get();
}
return 0;
}
위의 코드는 async를 이용해 Calculate를 비동기적으로 실행하고, 그 결과를 future 객체로 받는 예제이다. get()를 이용해 실제 결과가 나올 때까지 기다리고 받아오는 구조를 가지고 있는데 async의 매개변수로는 launch를 받는데 종류로는 2가지가 있다.
future는 결국 단순하게 하나의 함수만 잠시 비동기로 실행하고 싶을 때 사용하면 유용하다. future를 사용하는 방법은 크게 3가지가 있다.
future_status로 future의 상태를 의미한다.get을 한번 호출하면 기존의 future 객체는 empty가 된다. 즉, get을 여러번 호출하면 에러가 발생하게 된다.
future 객체를 얻는 방법에는 3가지가 존재한다.
위의 예제에서 사용한 방법으로 복잡한 쓰레드의 관리 없이 비동기 작업을 할 수 있는 가장 간단한 방법이다.
int main() {
{
std::future<int64> future = std::async(std::launch::async, Calculate);
// 중간에 작업 가능
// 실제 값을 가져옴
int64 sum = future.get();
}
return 0;
}
promise를 사용하는 방법은 get_future()를 이용해 promise-future쌍을 맞춰 다른 쓰레드에게 promise를 넘겨주고 future를 통해 값을 받아오는 방식으로 사용할 수 있다.
void PromiseWorker(std::promise<string>&& promise) {
promise.set_value("Secret Message");
}
int main() {
{
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();
}
return 0;
}
promise를 넘겨주는 방식으로 사용하기 때문에 PromiseWorkder의 매개변수로는 r-value promise로 설정하였다.
packaged_task를 사용하는 방법은 호출 가능한 객체(함수, 객체)에 대한 task를 이용하고 결과를 future를 통해 값을 받아오는 방식으로 사용한다.
void TaskWorker(std::packaged_task<int64(void)>&& task) {
task();
}
int main() {
{
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;
}
return 0;
}
위 3가지 방식을 정리한다면 다음과 같이 정리할 수 있다.
1) async: 원하는 함수를 비동기적으로 실행
2) promise: 결과물을 promise를 통해 future로 받아줌
3) packaged_task: 원하는 함수의 결과를 packaged_task를 통해 future로 받아줌