-
[Effective Modern C++] std::async의 launch policy (std::launch)C++/Effective Modern C++ 2023. 3. 28. 14:33
지난 글 (std::thread와 std::async)에서 동시성 프로그래밍을 위해 사용되는 두 기능을 살펴보았습니다.
몇몇 예외 상황을 제외하고, 여러 편의성을 이유로 std::async가 더 좋다는 내용이었습니다.
지난 글의 예제에서는 std::async를 다음과 같이 호출했습니다.
auto fut = std::async(function);
하지만 이런 호출에 대하여, function함수가 비동기적으로 바로 실행된다는 보장이 없습니다.
이번 글에서는 std::async의 작동 방식에 대하여 살펴보도록 하겠습니다.
1. std::launch
더보기std::async는 의미론적으로는 지정 객체를 비동기적으로 호출하는 의미이지만, 실제로는 그렇지 않습니다.
std::async는 특정한 실행 방침 (launch policy)에 따라 객체(함수)를 호출합니다.
이 때 지정하는 launch policy는 std::launch라는 enum class에 정의되어 있고, 그는 다음과 같습니다.
enum class launch { async = 0x1, deferred = 0x2 };
- std::launch::async는 비동기적으로 지정 객체(함수)를 호출합니다.
- std::launch::deferred는 std::async가 반환한 std;:future객체의 get 혹은 wait함수가 실행될 때 까지 대기합니다.
지정된 launch policy에 따라 std::async는 완전히 다른 방식으로 작동합니다.
2. std::async의 기본 실행 방식
더보기대부분의 상황에서, 우리는 비동기적인 실행을 기대하고 std::async를 사용할 것 입니다.
하지만 std::async의 기본 실행 방식은 비동기 실행이 아닙니다.
다음은 std::async의 오버로드 리스트 입니다.
template<class T, class... ArgT> future<T, ArgT> async(T&& cls, ArgT&&... Args); template<class T, class... ArgT> future<T, ArgT> async(launch launch, T&& cls, ArgT&&... Args);
공식 구현의 표현 중 불필요한 부분을 제외했습니다.
간단하게 보면, std::async는 std::launch를 받는 버전과 받지 않는 버전이 있습니다.
launch를 받지 않는 버전의 함수의 구현을 살펴보도록 하겠습니다.
template<class T, class... ArgT> future<T, ArgT> async(T&& cls, ArgT&&... Args) { return async( launch::async | launch::deferred, forward<T>(cls), forward<ArgT>(Args)... ); }
launch를 받지 않는 구현은, std::launch::async와 std::launch::defeered의 or연산으로 std::async를 호출합니다.
따라서, 기본 실행 방식은 비동기가 될 수도, 지연이 될 수도 있습니다.
이는 단점만 가지고 있지는 않습니다.
이러한 유연함이 std::async의 편의성을 만들어주는 요인이기 때문입니다.
하지만 이러한 유연성이 부정적으로 작용하는 상황도 존재합니다.
다음 문단에서 이어집니다.
3. 기본 실행 방식의 유연성
더보기다음과 같은 코드가 스레드 t에서 실행된다고 가정하겠습니다.
auto fut = std::async(f);
이 코드에 대하여 다음과 같은 추론이 가능합니다.
- f가 t와 동시에 실행되는 것을 예측할 수 없습니다. (f가 지연실행 될 수 있습니다.)
- f가 t와 다른 스레드에서 실행되는지 예측할 수 없습니다. (get이나 wait이 호출되는 스레드를 예측할 수 없습니다.)
- f가 반드시 실행될 것인지 예측할 수 없습니다. (get이나 wait이 반드시 호출된다는 보장이 없습니다.)
기본 실행 방식은 유연성을 보장하지만, 위와 같은 불확정성이 존재합니다.
특히 스레드의 경우, 스레드 로컬 저장소(TLS)에 접근하는 코드가 있다고 할 때에는 위와 같이 스레드의 동일 여부가 매우 중요해집니다.
그 외에도, 실행 여부를 예측할 수 없는 것 또한 문제 상황을 만들 수 있습니다.
이를 단적으로 보여줄 수 있는 예제가 있습니다.
using namespace std::literals; void f() { std::this_thread::sleep_for(1s); } auto fut = std::aysnc(f); while(fut.wait_for(100ms) != std::future_state::ready) { ... }
위 코드는 f의 실행이 끝날 때 까지 while문을 반복하는 코드입니다.
하지만 위와 같이 f를 인자로 fut객체를 생성했을 경우, fut가 곧바로 비동기적으로 실행된다는 보장이 없습니다.
wait_for함수의 반환값은 아래와 같습니다.
- std::future_state::ready - 값이 반환되어 준비된 상태입니다.
- std::future_state::timeout - wait_for의 대기 시간동안 값이 반환되지 않았습니다.
- std::future_state::deferred - 실행이 지연된 future객체의 상태입니다.
f가 비동기적으로 실행되면 위 코드의 wait_for함수는 timeout을 반복하다가 ready가 출력될 것 입니다.
하지만 defeered일 경우, get혹은 wait함수가 호출되지 않았기 때문에 무한루프가 발생합니다.
위 코드의 해결은 std::future_state::deferred에 대한 확인을 선행적으로 하는 것으로 해결됩니다.
하지만 해결책과 예외로, 기본 실행 방식을 이용하는 데 고려해야 할 점이 있다는 문제는 해결되지 않았습니다.
4. 기본 실행 방식의 사용
더보기std::async의 호출에 있어 기본 실행 방식을 사용할 때, 위와 같은 상황이 발생할 수 있음을 알아보았습니다.
따라서 기본 실행 방식을 사용할 때에는 다음과 같은 항목을 고려해봄이 바람직합니다.
- 과제(Task)가 get혹은 wait을 호출하는 스레드와 동시 실행될 필요성이 없습니다.
- 특정 스레드의 로컬 변수에 대한 접근 여부가 중요하지 않습니다.
- std::future객체에 대한 get혹은 wait함수가 반드시 호출된다는 보장이 존재하거나, 전혀 실행되지 않아도 괜찮습니다.
- 과제(Task)의 지연 여부가 wait_for, wait_until코드의 사용에 있어 고려되고 있습니다.
위 경우가 아니라면, std::async를 호출할 때에는 std::launch::async를 인자로 지정하는 것이 바람직합니다.
auto fut = std::async(std::launch::async, f);
위와 같은 코드를 여러 번 사용한다면, std::async를 Wrapping하는 것도 좋은 선택이 될 수 있습니다.
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)...); }
그리고 이는 위 문단에서 확인한, 기본 실행 방식을 사용하는 std::async의 구현과 유사합니다.
전체적으로, 유연성을 보장하기 위해 불확실성이 증가한 경우 같습니다.
하지만 조금만 신경쓴다면 의도하지 않은 행동이 일어나지도 않을 것이고, 코드의 의도도 명확해질 것이라 생각합니다.
이 글이 도움이 되셨기를 바랍니다.
감사합니다.
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] std::future의 소멸자 (0) 2023.04.19 [Effective Modern C++] std::thread를 unjoinable하게 만들어야 하는 이유 (0) 2023.04.14 [Effective Modern C++] std::thraed와 std::async (0) 2023.03.23 [Effective Modern C++] Lambda와 std::bind (0) 2022.10.03 [Effective Modern C++] Lambda : auto 매개변수 (0) 2022.09.25