티스토리 뷰

C・C++/멀티스레드

[C++] Future

jiggyjiggy 2024. 10. 4. 18:25

이전글 [C++] 스레드 경쟁: 스핀락, 슬립락 구현 / 이벤트, condition_variable (https://jiggyjiggy.tistory.com/137)에서, 스레드의 순서를 맞춰주기 위해 queue와 condition variable를 사용했다.

하지만 그보다 가벼운 상황일때도 존재한다. produce-consume 상황이 아닌, 단발성 이벤트의 경우가 그러하다. 그럴때, future 객체를 적용하면 좋다. (condition variable 보다 가볍고, C++ 11 표준 스펙)

상황


  
#include <iostream>
int Calculate()
{
int sum = 0;
for (int i = 0; i < 100'000; ++i)
{
sum += i;
}
return sum;
}
int main()
{
int sum = Calculate();
std::cout << sum << std::endl;
return 0;
}

Calculate 가 오래걸리므로, 단기알바(스레드)를 주자


  
int main()
{
int sum = Calculate();
std::cout << sum << std::endl;
std::thread t(Calculate);
t.join();
return 0;
}

그러면, 스레드에서 연산이 완료되면, 그 값을 받아와야 하는데,,

  • out 전용 변수(전역변수)로 받아올까?
    • 언제 완료되는지
    • 여러개가 있다면?
    • 일이 복잡해진다…
  • 스레드를 직접 만드는게 맞을까?
    • 잠시 사용할, 단기알바를 고용하자.
    • 스레드보다 더 가벼운 std::future !
      • 하나의 함수만 잠시 비동기로 실행하고싶을때 매우 유용 !
  • future 객체는 3가지 방법으로 만들고 사용한다.

future


  
int main()
{
{
std::future<int> future = std::async(std::launch::async, Calculate);
// TODO
int sum = future.get(); // 결과물이 이제서야 필요하다
}
return 0;
}
  • async 메서드
    • 1번째 인자: 정책 3가지
      • deferred: lazy evaluation (지연해서 실행하세요)
      • async : 별도의 스레드를 만들어서 실행하세요
      • deferred | async : 둘 중 알아서 골라주세요

일감이 진짜 끝났는지 peeking 하고 싶을 수 있다. 이런 경우 wait 계열 함수가 존재한다


  
int main()
{
{
std::future<int> future = std::async(std::launch::async, Calculate);
// TODO
std::future_status status = future.wait_for(std::chrono::milliseconds(1));
if (status == std::future_status::ready)
{
}
future.wait(); // == wait_for(INFINITY) == 결과물이 이 시점에 필요하다
int sum = future.get(); // 결과물이 이제서야 필요하다
}
return 0;
}

📝 요약해보면, future 객체는, 언젠가 미래에 결과물을 뱉는다는 약속을 받고있는 것이다

cf) 메서드를 future 객체에 등록하려면?

  • 추가적인 조건이 필요하다 (등록할 객체를 넣어줘야함)

  
class Knight
{
public:
int GetHp() { return 100; }
};
Knight knight;
std::future<int> future2 = std::async(std::launch::async, Knight::GetHp, knight);

promise

out 전용 변수로 구현해볼까? 근데 지저분하게는 싫어~

promise가 그 해결책이다!

다른 스레드에서 데이터를 밀어넣는 작업만 추가하고싶다고 가정해보자


  
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
  • promise는 다른 스레드에게 넘기고
  • future는 내가 갖고 있고

  
void PromiseWorker(std::promise<std::string>&& promise)
{
promise.set_value("Secret Message");
}
int main()
{
// std::promise
{
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
std::thread t(PromiseWorker, std::move(promise));
t.join();
}
return 0;
}
  • future는 main 스레드가 갖고있는다
  • promise의 소유권을, t 스레드에게 넘긴다
    • t 스레드가, promise.set_value("Secret Message") 를 통해 데이터를 입력하게 되면,
    • main 스레드에서 인지하고, future.get()을 통해서 받아 올 수 있게된다

  
void PromiseWorker(std::promise<std::string>&& promise)
{
promise.set_value("Secret Message");
}
int main()
{
// std::promise
{
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
std::thread t(PromiseWorker, std::move(promise));
std::string message = future.get();
std::cout << message << std::endl;
t.join();
}
return 0;
}

정리하면, promise에다 데이터를 넣어주고, future 객체를 통해서 받는 형태로 동작한다

packaged_task

Calculate 함수를 task로 등록하려면,


  
int Calculate()

인자는 void , 반환형은 int 이므로


  
// std::packaged_task
{
std::packaged_task<int(void)> task(Calculate);
std::future<int> future = task.get_future();
}

cf) promise와 packaged_task는 거의 비슷하다

약간의 차이를 설명하면,

  • promise
    • 다른 스레드에서, std::promisestd::string 에 맞는 타입의 데이터를 넣어주세요
  • packaged_task
    • 다른 스레드에서, std::packaged_task<int(void)> 라는 함수(Calculate)를 호출해주세요

이다


  
void TaskWorker(std::packaged_task<int(void)>&& task) // 소유권을 넘겨줄 것이기에 r-value
{
task();
}
int main()
{
// std::packaged_task
{
std::packaged_task<int(void)> task(Calculate);
std::future<int> future = task.get_future();
std::thread t(TaskWorker, std::move(task));
t.join();
}
return 0;
}
  • TaskWorker에서 특별히 하는 일은, “함수호출”이다
  • 특이한 점은, future를 통해서 받아올 수 있다는 것이다

전체코드


  
#include <iostream>
#include <thread>
#include <future>
int Calculate()
{
int sum = 0;
for (int i = 0; i < 100'000; ++i)
{
sum += i;
}
return sum;
}
void PromiseWorker(std::promise<std::string> &&promise)
{
promise.set_value("Secret Message");
}
void TaskWorker(std::packaged_task<int(void)> &&task)
{
task();
}
int main()
{
// std::future
{
std::future<int> future = std::async(std::launch::async, Calculate);
// TODO
std::future_status status = future.wait_for(std::chrono::milliseconds(1));
if (status == std::future_status::ready)
{
}
future.wait(); // == wait_for(INFINITY) == 결과물이 이 시점에 필요하다
int sum = future.get(); // 결과물이 이제서야 필요하다
// class Knight
// {
// public:
// int GetHp() { return 100; }
// };
// Knight knight;
// std::future<int> future2 = std::async(std::launch::async, Knight::GetHp, knight);
}
// std::promise
{
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
std::thread t(PromiseWorker, std::move(promise));
std::string message = future.get();
std::cout << message << std::endl;
t.join();
}
// std::packaged_task
{
std::packaged_task<int(void)> task(Calculate);
std::future<int> future = task.get_future();
std::thread t(TaskWorker, std::move(task));
int sum = future.get();
std::cout << sum << std::endl;
t.join();
}
return 0;
}

결론

  • mutex, condition_variable 까지 가지 않고, 단순한 애들을 처리할 수 있는 방법을 알아보았다
  • 특히, 1회성으로 일어나는 이벤트에대해서 매우 유용하다. producer-consumer 패턴으로서 큐에 데이터를 넣고 빼는 무한적인 작업이 아니라.
  • future를 만든 각 방법의 미묘한 차이를 간단하게 요약해보자
    • future
      • 스레드를 만들어서, Calculate를 시켰다
      • 태스크 하나를 위한 전용 스레드를 만들었다
      • 원하는 함수를 비동기적으로 실행
    • promise
      • 결과물을 promise를 통해서 future로 받아줌
    • packaged_task
      • 스레드를 만든 후, 태스크를 여러개 만들어서 넘길수도 있다
      • TaskWorker에서 인자를 수정해서 여러개 받게 만들수 있을 것이다
      • 원하는 함수의 실행 결과를 packaged_task를 통해 future로 받아줌

닭잡는데 소잡는 칼을 쓸 필요 없다!

cf)

비동기는 대부분 멀티스레드로 구현하겠지만,

비동기가 반드시 멀티스레드로서 구현되지는 않는다


  
std::async(std::launch::deferred, Calculate);
int sum = future.get();

라면, 실행시점에서 호출되는 것이다


  
std::async(std::launch::deferred, Calculate);
int sum = future.Calculate();
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
글 보관함