기억해 둘 사항들
- std::async의 기본 launch policy는 태스크의 비동기적 실행과 동기적 실행을 모두 허용
- 이러한 유연성 때문에 thread_local 접근의 불확실성이 발생하고, 태스크가 절대로 실행되지 않을 수 있고, wait 호출에 대한 프로그램 논리에도 영향을 미침
- 태스크를 반드시 비동기적으로 실행해야 한다면 std::launch::async를 지정
std::async의 launch policy
std::launch::async
반드시 비동기적으로(다른 스레드에서) 실행
std::launch::deferred
std::async가 돌려준 future 객체에 대해 get이나 wait이 호출될 때까지 함수 호출이 지연(deferred)됨
get이나 wait이 호출되면 동기적으로 실행됨
Default launch policy는 async | deferred
함수가 비동기로 실행될 수 있고 동기로 실행될 수 있음
따라서 스레드 라이브러리에서 과다구독 회피, 부하 균형화 등을 해결하는 과정에서 비동기 혹은 동기로 유연하게 실행될 수 있음
auto fut1 = std::async(f);
auto fut2 = std::async(std::launch::async | std::launch::deferred, f);
함수가 지연 실행될 수 있으므로 함수가 언제 실행될지 예측이 불가능
어떤 스레드에서 호출될 지 예측이 불가능
get이나 wait이 호출이 일어나는 보장이 없을 수도 있으므로, 함수가 실행될 것인지 예측하는 것이 불가능할 수도 있음
따라서 어떤 스레드를 사용할지 예측이 불가능하므로 thread_local 지역 변수와 같은 스레드 지역 저장소를 읽거나 쓸 수 없음
만약 f가 지연된다면 fut.wait_for는 항상 std::future_status::deferred를 반환하므로 무한루프가 발생할 가능성이 있음
using namespace std::literals;
void f(){
std::this_thread::sleep_for(1s);
}
std::future<void> fut = std::async(f);
while(fut.wait_for(100ms) != std::future_status::ready)
{
...
}
위에 말한 버그는 부하가 많이 걸리지 않으면 발생하지 않을 수 있으므로 개발과 단위 테스트에서 간과하기 쉬움
부하가 많이 걸리면 과다구독이나 스레드 고갈 현상이 발생하여 태스크가 지연될 가능성이 있음
따라서 지연되지 않았을 때에만 timeout 기반 루프에 진입해야 함
if (fut.wait_for(0s) == std::future_status::deferred)
{
...
}
else
{
while(fut.wait_for(100ms) != std::future_status::ready)
{
...
}
}
기본 launch policy를 사용하는 것은 다음 조건이 모두 성립할 때 적합
하나라도 성립하지 않는다면 비동기로 실행할 필요가 있음
- get이나 wait을 호출하는 스레드와 반드시 동시적으로 실행되지 않아도 됨
- 여러 스레드 중 어떤 스레드의 thread_local 변수를 읽고 쓰는지 중요하지 않음
- get이나 wait이 반드시 호출되는 보장이 있거나 태스크가 전혀 실행되지 않아도 됨
- 태스크가 지연될 수 있음을 고려한 구현
일일이 명시적으로 std::launch::sync를 지정하지 않아도 되는 함수 구현
// C+11
template<typename F, typename... Ts>
inline std::future<std::result_of_t<F(Ts...)>> reallyAsync(F&& f, Ts&&... params)
{
return std::async(std::launch::async, std::forward<F>(f), std::forward<Ts>(params)...);
}
// C++14
template<typename F, typename... Ts>
inline auto reallyAsync(F&& f, Ts&&... params)
{
return std::async(std::launch::async, std::forward<F>(f), std::forward<Ts>(params)...);
}
'C++ > Modern Effective C++' 카테고리의 다른 글
Modern Effective C++ 항목 38 스레드 핸들 소멸자들의 다양한 핸들 방식을 주의하라 (2) | 2024.11.29 |
---|---|
Modern Effective C++ 항목 37 std::thread들을 모든 경로에서 unjoinable로 만들어라 (0) | 2024.11.28 |
Modern Effective C++ 항목 35 스레드 기반 프로그래밍보다 과제(task) 기반 프로그래밍을 선호하라 (0) | 2024.11.25 |
Modern Effective C++ 항목 34 std::bind보다 람다를 선호하라 (0) | 2024.11.21 |
Modern Effective C++ 항목 33 std::forward를 통해서 전달할 auto&& 매개변수에는 decltype을 사용하라 (0) | 2024.11.20 |