C++/C++ 멀티스레드

C++ future(promise, async, packaged_task)

박성장 2022. 10. 15. 13:00
728x90

오늘은 C++의 future에 대해서 알아보겠습니다.

 

우리는 일반적으로 코드가 실행된다고 하면, 순차적으로 실행되는 것을 생각합니다.

 

1~10번까지의 코드가 있다면,

1번이 실행되고 2번이

2번이 실행되고 3번이 이렇게 말입니다.

 

그런데 만약 1번 명령문을 수행하는데에 아주 오랜 시간이 걸리면 어떻게 될까요?

2~10번코드는 1번 명령문이 완료되기를 기다리게 됩니다.

아주 오랜시간동안요.

 

하지만, 1번 명령문이 나머지 2~10번 코드에 영향을 미치는 코드가 아니라면?

1번이 반환하는 결과가 당장 필요하지 않다면?

 

그럴 때는 1번 명령을 어떤 스레드에게 맡겨놓고,

1번 명령이 완료되기 전에, 나머지 2~10번을 수행할 수 있을것입니다.

 

그리고, 이 때 1번 명령의 결과값을 저~기 7이나 8번즈음에서

받기 위해서 사용하는 방법이 future입니다.

 

future를 이용하기위해 제공되는 세 가지 방법 promise, async, packaged_task에 대해서 알아보겠습니다.


Promise

promise는 future에게 전달할 실행결과를 저장하는 것이라고 생각하면 됩니다.

 

코드를 먼저 보면 이해가 빠를 겁니다.

#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>

__int64 Calculate()
{
	__int64 sum = 0;
	for (int i = 0; i < 1000000; i++)
		sum += i;

	return sum;
}

void PromiseWork(std::promise<__int64>&& promise)
{
	promise.set_value(Calculate());
}

void main()
{
	//받아올 값을 저장할 promise를 생성!
	std::promise<__int64> promise;

	//promise에 저장'될' 값을 future를 통해 출력할 수 있다.
	std::future<__int64> future = promise.get_future();
    
    /*지금까지는 Calculate가 실행되지 않았지만, future를 통해서 그 결과를 받아오게 될것이다.*/

	//Calculate를 실행하고, promise에 그 결과값을 셋팅한다.
	//promise는 copy할 수 없고, move해야한다.
	std::thread t(PromiseWork, std::move(promise));

	//다른 작업ing...

	//다른 스레드가 실행한 Calculate의 결과값을 여기서 받아온다.
	//만약 실행이 끝나지 않았으면 block하고 끝날 때 까지 대기한다.
	__int64 sum = future.get();
	cout << sum << endl;

	t.join();
}
  • 참고사항
    • future는 get을 한 번만 호출할 수 있다.
    • promise는 set_value()를 한 번만 호출할 수 있다.

 


Async

 

번역하면, 비동기라는 의미인데

실핸순서가 선형이 아니라는 뜻입니다.

 

#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>

__int64 Calculate()
{
	cout << "start" << endl;
	__int64 sum = 0;
	for (int i = 0; i < 10000000; i++)
		sum += i;

	cout << "ends" << endl;
	return sum;
}

void main()
{
	std::future<__int64> future = std::async(std::launch::async, Calculate);
	//std::future<__int64> future = std::async(std::launch::deferred, Calculate);
	//std::future<__int64> future = std::async(Calculate);

	//다른 작업ing...

	__int64 sum = future.get();

	cout << sum << endl;
}

오히려 코드는 간단합니다.

 

async의 policy로 넘겨주는 인자에 대해서 알아보겠습니다.

  • std::launch::async : 호출하는 순간 새로운 스레드를 하나 생성해서, 함수를 실행합니다.
  • std::launch::deferred : future.get()을 하는 순간, 스레드를 생성하지 않고, 함수를 실행합니다.
  • default : async | deferred : C++런타임이 알아서 결정합니다.

packaged_task

#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>

__int64 Calculate()
{
	cout << "start" << endl;
	__int64 sum = 0;
	for (int i = 0; i < 10000000; i++)
		sum += i;

	cout << "ends" << endl;
	return sum;
}

void TaskWorker(std::packaged_task<__int64(void)>&& task)
{
	task();
}

void main()
{
	//return type, parameter type을 넘겨준다.
	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();
}

packaged_task는 promise를 자동으로 생성하고, 호출한 함수의 결과를  promise에 저장해준다.

 


언제쓰는가?

대표적으로 예외처리할 때 스레드끼리 exception을 주고받을 때 활용할 수 있다.

 

#include <iostream>
#include <thread>
#include <mutex>
#include <Windows.h>
#include <future>

int calculate()
{
	throw runtime_error("Exception thrown from calculate()");
}

int main()
{
	std::future<int> future = std::async(std::launch::async, calculate);

	//다른 작업ing...

	try{
		int result = future.get();
		cout << result << endl;
	}
	catch (exception e) {
		cout << e.what() << endl;
	}
}

 

728x90