오늘의논리 2024. 3. 13. 11:55
728x90

단발성 이벤트 같은 경우에는 조건변수까지 가지 않고 가벼운 방법이 존재한다.

 

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 까지 가지 않고 단순한 애들을 처리할 있는, 특히나 한번 발생하는 이벤트에 유용하다.

 

  1. Async : 원하는 함수를 비동기적으로 실행
  2. Promise : 결과물을 promise를통해 future 받아줌
  3. Packaged_task : 원하는 함수의 실행 결과를 packaged_task 통해 future 받아줌
728x90