C++ future, promise, packaged_task, async

은수·2022년 7월 12일

cpp study

목록 보기
21/21

동기 (synchronous) 와 비동기 (asynchronous) 실행

  • 동기적 처리 : 코드가 위에서부터 아래로 내려오면서, 하나가 끝나면 다음 코드가 실행되는 방식
  • 비동기적 처리 : 프로그램의 실행이, 한 갈래가 아니라 여러 갈래로 갈라져서 동시에 진행되는 것

std::promise 와 std::future

결국 비동기적 실행으로 하고 싶은 일은, 어떠한 데이터를 다른 쓰레드를 통해 처리해서 받아내는 것.

내가 어떤 쓰레드 T 를 사용해서, 비동기적으로 값을 받아내겠다 라는 의미는, 미래에 (future) 쓰레드 T 가 원하는 데이터를 돌려 주겠다 라는 약속 (promise)

#include <future>
#include <iostream>
#include <string>
#include <thread>
using std::string;

void worker(std::promise<string>* p) {
  // 약속을 이행하는 모습. 해당 결과는 future 에 들어간다.
  // promise객체가 future객체에 값을 넣어줌
  p->set_value("some data");
}
int main() {
  // 연산 수행 후 돌려줄 객체의 타입을 템플릿 인자로 받음 (= string)
  std::promise<string> p;

  // 미래에 string 데이터를 돌려 주겠다는 약속.
  std::future<string> data = p.get_future();

  std::thread t(worker, &p);



  // 미래에 약속된 데이터를 받을 때 까지 기다린다.
  data.wait();

  // wait 이 리턴했다는 뜻이 future 에 데이터가 준비되었다는 의미.
  // 따라서 wait함수가 리턴했다면 get을 통해 future에 전달된 객체 얻을 수 있음.
  // 참고로 wait 따로 호출하지않고 그냥 get 먼저 해도 wait 한 것과 같다.
  std::cout << "받은 데이터 : " << data.get() << std::endl;

  t.join();
}

std::promise<string> p;

promise 객체 정의 방법 : 연산 수행 후 돌려줄 객체의 타입을 템플릿 인자로 받음 (= string)

❗❗ 주의사항
future 에서 get 을 호출하면, 설정된 객체가 이동 됩니다. 따라서 절대로 get 을 두 번 호출하면 안됩니다.

정리
promise : 생산자-소비자 패턴에서 마치 생산자 (producer) 의 역할을 수행
future : 소비자 (consumer) 의 역할을 수행


wait_for

위의 코드처럼 그냥 wait을 사용하면, promise가 future에 전달할 때까지 기다림.

하지만 wait_for을 사용하면, 정해진 시간만 기다리고 그냥 진행 가능!

std::future_status status = data.wait_for(std::chrono::seconds(1));

// 아직 준비가 안됨
if (status == std::future_status::timeout) {
  cerr << ">";
}
// promise 가 future 를 설정함.
else if (status == std::future_status::ready) {
  break;
}

future_status의 3가지 상태

  1. future_status::ready : future에 값이 설정됨
  2. future_status::timeout :wait_for에 지정한 시간이 지났지만, 값이 설정되지 않음
  3. future_status::deferred : 결과값을 계산하는 함수가 실행되지 않음

shared_future

future 의 경우 딱 한 번만 get 을 할 수 있음.

  • get 을 호출하면 future 내부의 객체가 이동되기 때문

But, 종종 여러 개의 다른 쓰레드에서 future 를 get 할 필요성 존재. --> shared_future 사용!

shared_future
여러 개의 다른 쓰레드에서 future를 get할 때 사용.
future와 다르게 복사가 가능하고, 복사본들이 모두 같은 객체를 공유함. 따라서 레퍼런스 or 포인터로 전달할 필요 없음

packaged_task

packaged_task
promise-future 패턴을 비동기적 함수(정확히는 Callable - 즉 람다 함수, Functor 포함) 의 리턴값에 간단히 적용할 수 있게 해줌

std::packaged_task<int(int)> task(some_task);
std::future<int> start = task.get_future();
  • packaged_task 는 비동기적으로 수행할 함수 자체를 생성자의 인자로 받음 (=some_task)
  • 또한 템플릿 인자로 해당 함수의 타입을 명시해야 함 (=int)
  • packaged_task 는 전달된 함수를 실행해서, 그 함수의 리턴값을 promise 에 설정함.
  • 해당 promise 에 대응되는 future 는 위와 같이 get_future 함수로 얻을 수 있음
thread t(std::move(task), 5);
  • 이후, 생성된 packaged_task 를 쓰레드에 전달
  • 참고로 packaged_task 는 복사 생성이 불가능하므로 (promise 도 마찬가지) 명시적으로 move 해줘야함.
std::cout << "결과값 : " << start.get() << std::endl;
  • 비동기적으로 실행된 함수의 결과값은 추후에 future 의 get 함수로 받을 수 있음

즉, packaged_task 를 사용하게 된다면 쓰레드에 굳이 promise 를 전달하지 않아도 알아서 packaged_task 가 함수의 리턴값을 처리해줘서 매우 편리

std::async

promisepackaged_task 는 비동기적으로 실행을 하기 위해서는, 쓰레드를 명시적으로 생성해서 실행해야만 했음 ➡ std::async의 등장

std::async
아예 쓰레드를 알아서 만들어서 해당 함수를 비동기적으로 실행하고, 그 결과값을 future 에 전달해주는 객체

https://modoocode.com/285 쓰레드풀읽어보기

0개의 댓글