KoreanFoodie's Study

[C++ 게임 서버] 1-11. future, promise, packaged_task 본문

Game Dev/Game Server

[C++ 게임 서버] 1-11. future, promise, packaged_task

GoldGiver 2023. 7. 18. 16:32

Rookiss 님의 '[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part4: 게임 서버' 를 들으며 배운 내용을 정리하고 있습니다. 관심이 있으신 분은 꼭 한 번 들어보시기를 추천합니다!

[C++ 게임 서버] 1-11. future, promise, packaged_task

핵심 :

1. future, promise, packaged_task 는 단발성 이벤트를 비동기적으로 실행할 때 유용하다.

2. 세 녀석의 차이는 Level of Control 에 달려 있다. future 는 특정 함수의 작업을 맡기고, packaged_task 는 작업을 만든 다음, 쓰레드에 이를 실어 보낸다. 마지막으로, promise 는 작업을 실어 보내면서, 동시에 실어 보내는 함수 내부에서 future 에 넘길 값을 커스텀하게 세팅할 수 있다.

3. '비동기적'인 작업과 '멀티쓰레드' 는 다른 의미이다. 어떤 작업이 비동기적으로 실행된다고 해서, 꼭 멀티쓰레드를 이용할 필요는 없다.

사실 자주 쓰이는 녀석들은 아니지만, 간단한 일회성 이벤트의 경우 future, promise, packaged_task 를 사용하면 복잡하게 lock 과 관련된 사항들을 체크할 필요없이 비동기적인 작업을 효율적으로 수행할 수 있다.

아래 예제 코드를 보면, 이해가 한 번에 될 것이다.

int64 Calculate()
{
	int64 sum = 0;
	for (int64 i = 0; i < 100'000; ++i)
	{
		sum += i;
	}

	return sum;
}

void PromiseWorker(promise<string>&& InPromise)
{
	InPromise.set_value("Secret Message");
}

void TaskWorker(packaged_task<int64(void)>&& task)
{
	task();
}

int main()
{
	// Future
	// 실제로 원하는 값을 사용할 때 작업이 진행됨.
	// 락을 사용하지 않는 간단한 작업
	{
		// Future 를 만들 때 3가지 옵션이 있음
		// 1. launch::async		: 다른 쓰레드를 만들어서 작업을 맡김
		// 2. launch::deferred  : 동일한 쓰레드 내에서, 실제로 get 을 할 때 값을 계산 (Lazy Evaluation)
		// 3. deferred | async	: 컴파일러에게 어떤 옵션을 수행할지 맡김
		future<int64> f = std::async(launch::async, Calculate);

		// Do Something

		// 결과물이 이제서야 필요함
		int64 sum = f.get(); 

		cout << "Future Sum : " << sum << endl;
	}


	// Promise
	// 미래(std::future) 에 결과물을 반환해거라 약속(std::promise) 해줘~ (계약서)
	{
		promise<string> promiseRes;
		future<string> futureRes = promiseRes.get_future();

		thread t(PromiseWorker, std::move(promiseRes));

		string message = futureRes.get();
		cout << "Promise : " << message << endl;

		t.join();
	}

	// Packaged Task
	{
		packaged_task<int64(void)> task(Calculate);
		future<int64> future = task.get_future();

		thread t(TaskWorker, std::move(task));

		int64 sum = future.get();
		cout << "Packaged Task Sum : " << sum << endl;

		t.join();
	}

	// 결론)
	// mutex, condition_variable 을 사용하지 않고 간단한 작업을 맡김
	// 특히나, 한 번 발생하는 이벤트에 유용
	// 닭잡는데 소잡는 칼을 쓸 필요없다!

	// 1) async
	// 원하는 함수를 비동기적으로 실행
	// 2) promise
	// 결과물을 promise 를 통해 future 로 받아줌
	// 3) packaged_task
	// 원하는 함수의 실행 결과를 packaged_task 를 통해 future 로 받아줌
}

실제로 promise 와 packaged_task 는 현업에서 거의 볼 일이 없다고 한다. 실제로도 future 로 대부분의 케이스를 처리할 수 있을 것으로 보인다 😅

 

사실 세 녀석의 차이는 Level of Control 에 달려 있다.

future 는 특정 함수의 작업을 맡긴다. 그게 끝이다. 맡기면 그냥 맡긴 일에 대한 결과를 get 을 할 때 확인한다.

packaged_task 는 작업을 만든 다음, 쓰레드에 이를 실어 보낸다. 쓰레드를 직접 만드니까 여러 쓰레드에 이를 넘길 수 있고, 작업을 수행하는 쓰레드를 재활용할 수도 있다.

마지막으로, promise 는 작업을 실어 보내면서, 동시에 실어 보내는 함수 내부에서 future 에 넘길 값을 커스텀하게 세팅할 수 있다. 즉, 작업을 맡기되, 실제 get 을 할때의 값을 작업을 해 주는 함수 내부에서 조작할 수도 있다(물론 값을 받고 나서 조작을 할 수도 있겠지만, 조건문이 필요한 경우에는 해당 함수에서 처리하는 것이 더 깔끔할 것이다).

 

세 녀석에 대한 차이를 좀 더 자세히 알고 싶으면 이 링크를 참고하자 😉

Comments