안녕하세요. 명월입니다.
이 글은 C++에서의 async, promise, future, packaged_task의 사용법에 대한 글입니다.
이전에 제가 쓰레드에 대해서 간략하게 설명한 적이 있습니다.
링크 - [C++] 쓰레드(Thread)를 사용하는 방법
쓰레드란 main 함수에서 병렬 처리가 가능한 것을 뜻하다고 설명했습니다. 그리고 각 쓰레드에서 사용되는 값을 동기화 하기 위해서는 mutex의 lock를 이용합니다.
그런데 이번에는 같은 병렬처리이기는 하는데 값을 동기화하여 공유하는 것이 아니라 함수처럼 결과 return을 기다리는 식으로 작성하고 싶을 때가 있습니다.
그럴 때는 이 promise와 future를 이용하면 각 병렬 처리간에 값을 주고 받기가 쉽습니다.
#include <stdio.h>
#include <iostream>
// promise와 future를 이용하기 위한 라이브러리
#include <future>
// thread를 이용하기 위한 라이브러리
#include <thread>
using namespace std;
// 쓰레드를 위한 함수 (파라미터는 결과를 받기 위한 promise, 반복문의 횟수)
void thread_function(promise<int>* p, int count)
{
// 결과 변수
int ret = 0;
// 반복문의 횟수만큼 더한다.
for (int i = 0; i < count; i++)
{
// count가 10이면 0부터 10까지 더하는 함수이다.
ret += i;
}
// 결과를 set_value로 넘긴다.
p->set_value(ret);
}
// 실행 함수
int main()
{
// promise 선언
promise<int> p;
// promise와 future를 연결.
// promise에서 set_value를 하면 future에서 get으로 받는 것이다.
future<int> data = p.get_future();
// 쓰레드를 실행하여 promise를 던지고, 반복문의 횟수를 10을 설정한다.
thread _th(thread_function, &p, 10);
// data.get()을 하면 promise가 set_value할 때까지 기다린다.
// 내부적으로 data.wait이 실행된다.
cout << data.get() << endl;
// thread를 종료한다.
_th.join();
return 0;
}
여기서 값을 주고 받는 기능을 하는 건 promise의 set_value함수와 future의 get함수입니다.
javascript의 promise를 생각해서 promise의 set_value함수를 두번 호출하면 에러가 발생합니다.
future의 경우도 get을 여러번 호출하면 에러가 발생합니다..일회용입니다.그러나 future만은 재사용이 가능한 객체가 있습니다.
#include <stdio.h>
#include <iostream>
// promise와 future를 이용하기 위한 라이브러리
#include <future>
// thread를 이용하기 위한 라이브러리
#include <thread>
using namespace std;
// 쓰레드를 위한 함수 (파라미터는 결과를 받기 위한 promise, 반복문의 횟수)
void thread_function(promise<int>* p, int count)
{
// 결과 변수
int ret = 0;
// 반복문의 횟수만큼 더한다.
for (int i = 0; i < count; i++)
{
// count가 10이면 0부터 10까지 더하는 함수이다.
ret += i;
}
// 결과를 set_value로 넘긴다.
p->set_value(ret);
}
// 실행 함수
int main()
{
// promise 선언
promise<int> p;
// promise와 share_future를 연결.
// share_future의 경우는 get을 한번만 사용하는 것이 아니고 여러번 사용가능합니다.
share_future<int> data = p.get_future();
// 쓰레드를 실행하여 promise를 던지고, 반복문의 횟수를 10을 설정한다.
thread _th(thread_function, &p, 10);
// data.get()을 하면 promise가 set_value할 때까지 기다린다.
cout << data.get() << endl;
cout << data.get() << endl;
cout << data.get() << endl;
cout << data.get() << endl;
// thread를 종료한다.
_th.join();
return 0;
}
참조 - http://www.cplusplus.com/reference/future/promise/
여기서 promise와 future를 사용하기 위해서는 결국 함수에 파라미터로 promise를 설정해야 합니다.
기존에 있는 함수를 수정할 수 없는 상황에서 비동기를 사용하려고 한다면 promise를 사용할 수 없겠습니다.
이걸 극복한 것이 packaged_task입니다.
#include <stdio.h>
#include <iostream>
// promise와 future를 이용하기 위한 라이브러리
#include <future>
// thread를 이용하기 위한 라이브러리
#include <thread>
using namespace std;
// 쓰레드를 위한 함수 (promise 파라미터를 빼고 일반 함수 형태이다.)
int thread_function(int count)
{
int ret = 0;
// count까지 더한다
for (int i = 0; i < count; i++)
{
ret += i;
}
// 결과 리턴
return ret;
}
// 실행 함수
int main()
{
// packaged_task 선언
packaged_task<int(int)> task(thread_function);
// packaged_task와 share_future를 연결.(promise와 비슷한다.)
shared_future<int> data = task.get_future();
// move함수를 사용하여 task를 쓰레드에 넘긴다.
thread _th(move(task), 10);
// data.get()을 하면 함수가 종료될 때까지 기다린다.
cout << data.get() << endl;
cout << data.get() << endl;
cout << data.get() << endl;
cout << data.get() << endl;
_th.join();
return 0;
}
위 promise와 packaged_task의 결과는 같습니다. 그러나 쓰레드를 위한 함수의 리턴과 파라미터 타입이 바뀌었습니다. packaged_task를 사용하면 이전에 작성했던 함수들도 수정없이도 병렬 처리가 가능하다는 뜻입니다.
참조 - http://www.cplusplus.com/reference/future/packaged_task/
여기서 이 promise와 packaged_task의 최종 보스는 async입니다.
async란 녀석은 promise나 packaged_task, thread 객체까지 전부 다 생략해 버린 것입니다.
#include <stdio.h>
#include <iostream>
// promise와 future를 이용하기 위한 라이브러리
#include <future>
// thread를 이용하기 위한 라이브러리
#include <thread>
using namespace std;
// 쓰레드를 위한 함수 (promise 파라미터를 빼고 일반 함수 형태이다.)
int thread_function(int count)
{
int ret = 0;
// count까지 더한다
for (int i = 0; i < count; i++)
{
ret += i;
}
// 결과 리턴
return ret;
}
// 실행 함수
int main()
{
// async에 함수명과 파라미터를 설정, 리턴은 future이다.
shared_future<int> data = async(thread_function, 10);
// data.get()을 하면 함수가 종료될 때까지 기다린다.
cout << data.get() << endl;
cout << data.get() << endl;
cout << data.get() << endl;
cout << data.get() << endl;
return 0;
}
역시 결과는 같습니다. 보면 소스가 더 단순해 지고 소스 스텝이 더 줄었습니다. 여기서 람다식까지 넣으면...
#include <stdio.h>
#include <iostream>
// promise와 future를 이용하기 위한 라이브러리
#include <future>
// thread를 이용하기 위한 라이브러리
#include <thread>
using namespace std;
// 실행 함수
int main()
{
// async에 람다식을 넣고, 파라미터는 10을 넣었습니다. 결과는 future로 받으니 get을 바로 호출하면 함수값이 바로나온다.
int ret = async([](int count) -> int {
int ret = 0;
for (int i = 0; i < count; i++)
{
ret += i;
}
return ret;
}, 10).get();
// 콘솔 출력
cout << "ret - " << ret << endl;
return 0;
}
참조 - http://www.cplusplus.com/reference/future/async/?kw=async
async까지 오니깐 c++소스가 무슨 javascript 소스처럼 변해 버렸네요.
promise와 future, packaged_task, async는 제가 c++를 한창 할 때도 모르던 키워드이고 객체입니다.
우연히 thread pool에 대해서 조사하던 중, async 키워드가 있는 것을 발견하고 파고들다 보니 여기까지 알게 되었네요.. 참 c++를 한창 다룰 때, 이런 기능을 알았으면 참 좋았을 텐데라는 아쉬움이 있네요.
여기까지 C++에서의 async, promise, future, task의 사용법에 대한 글이었습니다.
궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.
'Study > C , C++ , MFC' 카테고리의 다른 글
[C++] C++에서 Window Form을 생성하는 방법(Win32Api, MFC) (0) | 2020.05.01 |
---|---|
[C++] 함수 포인터 사용법 (0) | 2020.04.20 |
[C++] 소켓(Socket) 통신을 하는 방법 (0) | 2020.04.14 |
[C++] map(맵)의 사용법 (0) | 2020.04.09 |
[C++] vector(리스트)의 사용법 (Stack, Queue 알고리즘 예제) (0) | 2020.04.08 |
[C++] 쓰레드(Thread)를 사용하는 방법 (0) | 2020.04.07 |
[C++] IO (fstream)(파일 읽기 쓰기)를 사용하는 방법 (0) | 2020.04.06 |
[C++] 예외처리(try ~ catch, throw) 사용법 (2) | 2020.04.04 |