future
단발성 이벤트 같은 경우에는 조건변수까지 가지 않고 더 가벼운 방법이 존재한다.
C++의 `std::future`는 비동기 작업의 결과를 저장하고 검색하는 데 사용되는 클래스 템플릿이다.
`std::future`는 별도의 스레드에서 실행되는 함수의 결과를 검색하는 작업을 간소화한다. 결과는 함수에서 반환되는 값 또는 함수에서 발생했지만 함수에 catch되지 않은 예외 중 하나이다.
`std::future`는 주로 `std::async`, `std::promise`, `std::packaged_task`와 함께 사용된다. 이들은 모두 비동기 작업을 수행하고 그 결과를 `std::future`를 통해 검색하는 데 사용된다.
`std::future`는 다음과 같은 주요 멤버 함수를 제공한다.
- `get`: 비동기 작업의 결과를 반환한다. 이 함수는 한 번만 호출할 수 있으며, 두 번째 호출은 정의되지 않은 동작을 초래한다.
- `wait`: 비동기 작업이 완료될 때까지 대기한다.
- `wait_for`와 `wait_until`: 비동기 작업이 완료될 때까지 특정 시간 동안 대기한다.
이러한 기능들을 통해 `std::future`는 비동기 프로그래밍을 보다 쉽게 구현할 수 있게 도와준다.
사용하기 위해서는 <future>를 include 해줘야 한다.
예제를 보겠다.
int64 Calculate() //복잡한 계산을 함수가 있다고 한다면
{
int64 sum = 0;
for (int32 i = 0; i < 100'000; ++i)
sum += i;
return sum;
}
int main()
{
//동기(synchronous) 실행
int64 sum = Calculate();
cout << sum << endl;
return 0;
}
이경우 라면 만약 Calculate 함수가 100초가 걸린다고 한다면 100초동안 아무것도 못한채로 있게될 것이다.
int main()
{
thread t1(Calculate);
t1.join();
return 0;
}
하지만 이렇게 쓰레드를 사용하면 일을 쓰레드에게 시켰으니 잘 진행될 것이다. 하지만 저런 일 하나 하는데 쓰레드를 사용하는 것이 맞나 싶을 것이다. 그래서 이렇게 무겁게 쓰레드를 사용하는 것 보다 가벼운 방법이 있는데 그것이 Future 이다.
#include <iostream>
#include <thread>
#include <atomic>
#include <mutex>
#include <Windows.h>
#include <Queue>
#include <future>
int64 Calculate() //복잡한 계산을 함수가 있다고 한다면
{
int64 sum = 0;
for (int32 i = 0; i < 100'000; ++i)
sum += i;
return sum;
}
int main()
{
//동기(synchronous) 실행
//int64 sum = Calculate();
//cout << sum << endl;
//비동기 실행
{
/*3가지 옵션*/
//1)deferred ->lazy evaluation 지연해서 실행하세요
//2)async -> 별도의 쓰레드를 만들어서 실행하세요
//3)deferred | async -> 둘중 알아서 골라주세요
//잠시 미뤘다가 나중에 실행함. (현재 일감이 많이 밀려있을 때 유용 할 수 있음)
//std::future<int64> future = std::async(std::launch::deferred, Calculate);
//별도의 쓰레드가 하나 생성되어 Calculate 함수를 병렬로 실행하게 됨.
std::future<int64> future = std::async(std::launch::async, Calculate);
//다른행동 가능
//얻어오고싶을때
int64 sum = future.get();
}
return 0;
}
이러한 방식은 간단한 기능(예를들어 로딩)인데 이런걸 쓰레드를 만들고 생성하고 관리하는것 보다는 이런 방식으로 이용하는것이 유용 할 수 있다. 근데 경우에 따라서 이 일감이 얼마나 되었는지 확인하고 싶을 때가 있을 것이다.
//1ms 가 지났을때 얼마나 되어있는지
std::future_status status = future.wait_for(1ms);
if (status == future_status::ready)
{
//일감이 완료 되었을때
}
그럴땐 위 코드와 같이 status 라는 값을 wait_for 함수를 통해 받아올 수 있다. 그리고 클래스의 멤버 함수를 future로 사용하고싶을때는
class Player
{
public:
int64 Move()
{
return 10;
}
};
Player p1;
std::future<int64> future2 = std::async(std::launch::async, &Player::Move, p1);//p1.Move();
이런식으로 객체를 생성해서 객체까지 넣어주어야 한다.
Future 객체를 만들어주는 또하나의 방법인 promise 에 대해 알아보겠다. promise 는 futrue 에 결과물을 반환해 줄 것을 약속하는(계약하는?) 방법이다.
void PromiseWorker(std::promise<string>&& promise)
{
promise.set_value("Message!");
}
이런 함수가 있을 때
{
std::promise<string> promise;//미래(std::future)에 결과물을 반환해줄거라는것을 약속함.
std::future<string> future = promise.get_future(); // 위와 연동이 된상태
thread t(PromiseWorker, std::move(promise));
string message = future.get(); //get 시 데이터를 이동시키기 때문에 이동 후에는 유효하지 않은 데이터가 됨. 딱 한번만 호출해야함
cout << message << endl;
t.join();
}
실행시
잘 출력되는 것을 볼 수 있다.
또하나의 방법으로는 std::packaged_task 라는 것이 있는데
void TaskWorker(std::packaged_task<int64(void)> && task)
{
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();
}
위와 같이 사용 할 수 있다.
결론) mutex나 condition_variable 까지 가지 않고 단순한 애들을 처리할 수 있는, 특히나 한번 발생하는 이벤트에 유용하다.
- Async : 원하는 함수를 비동기적으로 실행
- Promise : 결과물을 promise를통해 future 로 받아줌
- Packaged_task : 원하는 함수의 실행 결과를 packaged_task를 통해 future 로 받아줌