[C++ 서버] Future

이정석·2023년 11월 22일

CppServer

목록 보기
6/8

Future

1. 동기적 실행

기존에 함수를 실행해 결과를 받아오는 방법은 보통 '동기적'으로 결과를 가져온다고 표현하는데 아래의 간단한 예제를 보자.

  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전까지 별도의 작업을 수행할 수 있지만, 쓰레드를 위한 공유 데이터나 쓰레드를 생성, 소멸하는데에 드는 비용이 비효율적일 수도 있다.

2. Future

문자 그대로 미래객체 특정 연산이 완료될 때까지 기다릴 필요 없이 다른 작업을 계속할 수 있게 해준다. 즉, 비동기작업을 수행할 수 있는 방법 중 하나로 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가지가 있다.

  1. deferred: 지연해서 실행, 실제 함수 실행은 get()을 호출할 때 실행된다.
  2. async: 별도의 쓰레드를 만들어서 실행, future를 얻을 때 다른 쓰레드에서 함수가 실행되고 값은 이후에 get()을 통해 받는다.

future는 결국 단순하게 하나의 함수만 잠시 비동기로 실행하고 싶을 때 사용하면 유용하다. future를 사용하는 방법은 크게 3가지가 있다.

  1. get(): 비동기 작업의 결과를 얻는다.
  2. wait(): 비동기 작업이 완료될 때까지 기다린다.
  3. wait_for(ms): 지정된 시간동안 작업이 완료되기를 기다린다. 반환형은 future_statusfuture의 상태를 의미한다.

get을 한번 호출하면 기존의 future 객체는 empty가 된다. 즉, get을 여러번 호출하면 에러가 발생하게 된다.

3. Future를 얻는 방법

future 객체를 얻는 방법에는 3가지가 존재한다.

  1. async

위의 예제에서 사용한 방법으로 복잡한 쓰레드의 관리 없이 비동기 작업을 할 수 있는 가장 간단한 방법이다.

  int main() {
      {
          std::future<int64> future = std::async(std::launch::async, Calculate);
          // 중간에 작업 가능
          
          
          // 실제 값을 가져옴
          int64 sum = future.get();
      }
      return 0;
  }
  1. promise

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로 설정하였다.

  1. packaged_task

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로 받아줌

profile
게임 개발자가 되고 싶은 한 소?년

0개의 댓글