티스토리 뷰
이전글 [C++] 스레드 경쟁: 스핀락, 슬립락 구현 / 이벤트, condition_variable (https://jiggyjiggy.tistory.com/137)에서, 스레드의 순서를 맞춰주기 위해 queue와 condition variable를 사용했다.
하지만 그보다 가벼운 상황일때도 존재한다. produce-consume 상황이 아닌, 단발성 이벤트의 경우가 그러하다. 그럴때, future 객체를 적용하면 좋다. (condition variable 보다 가볍고, C++ 11 표준 스펙)
상황
#include <iostream>
int Calculate()
{
int sum = 0;
for (int i = 0; i < 100'000; ++i)
{
sum += i;
}
return sum;
}
int main()
{
int sum = Calculate();
std::cout << sum << std::endl;
return 0;
}
Calculate 가 오래걸리므로, 단기알바(스레드)를 주자
int main()
{
int sum = Calculate();
std::cout << sum << std::endl;
std::thread t(Calculate);
t.join();
return 0;
}
그러면, 스레드에서 연산이 완료되면, 그 값을 받아와야 하는데,,
- out 전용 변수(전역변수)로 받아올까?
- 언제 완료되는지
- 여러개가 있다면?
- 일이 복잡해진다…
- 스레드를 직접 만드는게 맞을까?
- 잠시 사용할, 단기알바를 고용하자.
- 스레드보다 더 가벼운 std::future !
- 하나의 함수만 잠시 비동기로 실행하고싶을때 매우 유용 !
- future 객체는 3가지 방법으로 만들고 사용한다.
future
int main()
{
{
std::future<int> future = std::async(std::launch::async, Calculate);
// TODO
int sum = future.get(); // 결과물이 이제서야 필요하다
}
return 0;
}
- async 메서드
- 1번째 인자: 정책 3가지
- deferred: lazy evaluation (지연해서 실행하세요)
- async : 별도의 스레드를 만들어서 실행하세요
- deferred | async : 둘 중 알아서 골라주세요
- 1번째 인자: 정책 3가지
일감이 진짜 끝났는지 peeking 하고 싶을 수 있다. 이런 경우 wait 계열 함수가 존재한다
int main()
{
{
std::future<int> future = std::async(std::launch::async, Calculate);
// TODO
std::future_status status = future.wait_for(std::chrono::milliseconds(1));
if (status == std::future_status::ready)
{
}
future.wait(); // == wait_for(INFINITY) == 결과물이 이 시점에 필요하다
int sum = future.get(); // 결과물이 이제서야 필요하다
}
return 0;
}
📝 요약해보면, future 객체는, 언젠가 미래에 결과물을 뱉는다는 약속을 받고있는 것이다
cf) 메서드를 future 객체에 등록하려면?
- 추가적인 조건이 필요하다 (등록할 객체를 넣어줘야함)
class Knight
{
public:
int GetHp() { return 100; }
};
Knight knight;
std::future<int> future2 = std::async(std::launch::async, Knight::GetHp, knight);
promise
out 전용 변수로 구현해볼까? 근데 지저분하게는 싫어~
promise가 그 해결책이다!
다른 스레드에서 데이터를 밀어넣는 작업만 추가하고싶다고 가정해보자
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
- promise는 다른 스레드에게 넘기고
- future는 내가 갖고 있고
void PromiseWorker(std::promise<std::string>&& promise)
{
promise.set_value("Secret Message");
}
int main()
{
// std::promise
{
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
std::thread t(PromiseWorker, std::move(promise));
t.join();
}
return 0;
}
- future는 main 스레드가 갖고있는다
- promise의 소유권을, t 스레드에게 넘긴다
- t 스레드가, promise.set_value("Secret Message") 를 통해 데이터를 입력하게 되면,
- main 스레드에서 인지하고, future.get()을 통해서 받아 올 수 있게된다
void PromiseWorker(std::promise<std::string>&& promise)
{
promise.set_value("Secret Message");
}
int main()
{
// std::promise
{
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
std::thread t(PromiseWorker, std::move(promise));
std::string message = future.get();
std::cout << message << std::endl;
t.join();
}
return 0;
}
정리하면, promise에다 데이터를 넣어주고, future 객체를 통해서 받는 형태로 동작한다
packaged_task
Calculate 함수를 task로 등록하려면,
int Calculate()
인자는 void , 반환형은 int 이므로
// std::packaged_task
{
std::packaged_task<int(void)> task(Calculate);
std::future<int> future = task.get_future();
}
cf) promise와 packaged_task는 거의 비슷하다
약간의 차이를 설명하면,
- promise
- 다른 스레드에서, std::promisestd::string 에 맞는 타입의 데이터를 넣어주세요
- packaged_task
- 다른 스레드에서, std::packaged_task<int(void)> 라는 함수(Calculate)를 호출해주세요
이다
void TaskWorker(std::packaged_task<int(void)>&& task) // 소유권을 넘겨줄 것이기에 r-value
{
task();
}
int main()
{
// std::packaged_task
{
std::packaged_task<int(void)> task(Calculate);
std::future<int> future = task.get_future();
std::thread t(TaskWorker, std::move(task));
t.join();
}
return 0;
}
- TaskWorker에서 특별히 하는 일은, “함수호출”이다
- 특이한 점은, future를 통해서 받아올 수 있다는 것이다
전체코드
#include <iostream>
#include <thread>
#include <future>
int Calculate()
{
int sum = 0;
for (int i = 0; i < 100'000; ++i)
{
sum += i;
}
return sum;
}
void PromiseWorker(std::promise<std::string> &&promise)
{
promise.set_value("Secret Message");
}
void TaskWorker(std::packaged_task<int(void)> &&task)
{
task();
}
int main()
{
// std::future
{
std::future<int> future = std::async(std::launch::async, Calculate);
// TODO
std::future_status status = future.wait_for(std::chrono::milliseconds(1));
if (status == std::future_status::ready)
{
}
future.wait(); // == wait_for(INFINITY) == 결과물이 이 시점에 필요하다
int sum = future.get(); // 결과물이 이제서야 필요하다
// class Knight
// {
// public:
// int GetHp() { return 100; }
// };
// Knight knight;
// std::future<int> future2 = std::async(std::launch::async, Knight::GetHp, knight);
}
// std::promise
{
// 미래(std::future)에 결과물을 반환해줄꺼라 약속(std::promise)해줘. (aka. 계약서)
std::promise<std::string> promise;
std::future<std::string> future = promise.get_future();
std::thread t(PromiseWorker, std::move(promise));
std::string message = future.get();
std::cout << message << std::endl;
t.join();
}
// std::packaged_task
{
std::packaged_task<int(void)> task(Calculate);
std::future<int> future = task.get_future();
std::thread t(TaskWorker, std::move(task));
int sum = future.get();
std::cout << sum << std::endl;
t.join();
}
return 0;
}
결론
- mutex, condition_variable 까지 가지 않고, 단순한 애들을 처리할 수 있는 방법을 알아보았다
- 특히, 1회성으로 일어나는 이벤트에대해서 매우 유용하다. producer-consumer 패턴으로서 큐에 데이터를 넣고 빼는 무한적인 작업이 아니라.
- future를 만든 각 방법의 미묘한 차이를 간단하게 요약해보자
- future
- 스레드를 만들어서, Calculate를 시켰다
- 태스크 하나를 위한 전용 스레드를 만들었다
- 원하는 함수를 비동기적으로 실행
- promise
- 결과물을 promise를 통해서 future로 받아줌
- packaged_task
- 스레드를 만든 후, 태스크를 여러개 만들어서 넘길수도 있다
- TaskWorker에서 인자를 수정해서 여러개 받게 만들수 있을 것이다
- 원하는 함수의 실행 결과를 packaged_task를 통해 future로 받아줌
- future
닭잡는데 소잡는 칼을 쓸 필요 없다!
cf)
비동기는 대부분 멀티스레드로 구현하겠지만,
비동기가 반드시 멀티스레드로서 구현되지는 않는다
std::async(std::launch::deferred, Calculate);
int sum = future.get();
라면, 실행시점에서 호출되는 것이다
std::async(std::launch::deferred, Calculate);
int sum = future.Calculate();
'C・C++ > 멀티스레드' 카테고리의 다른 글
[C++] 멀티 스레드로 소수 구하기 (0) | 2024.10.05 |
---|---|
[C++] Reader-Writer Lock 구현 (0) | 2024.10.05 |
[C++] Thread Local Storage (aka. TLS) (0) | 2024.10.04 |
[C++] 멀티스레드 경쟁과 생상자-소비자 문제: 스핀락, 슬립락 구현 / 이벤트, condition_variable (2) | 2024.10.04 |
[C++] Lock-Based Stack 구현 (1) | 2024.10.03 |
- Total
- Today
- Yesterday
- servlet
- CPU
- S1
- reader-writer lock
- 엔티티 설계 주의점
- PS
- generic sort
- 백준
- tree
- 이진탐색
- thread
- Spring MVC
- 연관관계 편의 메서드
- condition variable
- Dispatcher Servlet
- generic swap
- S4
- JPA
- 논문추천
- 객체 변조 방어
- tomcat11
- C
- core c++
- 톰캣11
- Memory
- pocu
- sleep lock
- 개발 공부 자료
- OOP
- Java
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |