스토리텔링 개발자

[Effective Modern C++] 36. std::async의 시동 방침(launch policy) 본문

Effective C++/Effective Modern C++

[Effective Modern C++] 36. std::async의 시동 방침(launch policy)

김디트 2025. 4. 10. 11:15
728x90

항목 36. 비동기성이 필수일 때에는 std::launch::async를 지정하라

 

 

 

std::async 호출
  • 함수를 비동기적으로 실행하겠다는 의미
    • 하지만 늘 그런 의미일 필요는 없다.
  • 함수를 어떤 시동 방침(launch policy)에 따라 실행한다는 의미도 가진다.

 

 

표준이 제공하는 시동 방침
  • std::launch 라는 enum으로 제공된다.
  • std::launch::async
    • f는 반드시 비동기적으로(다른 스레드에서) 실행된다.
  • std::launch::deferred
    • f는 std::async가 리턴한 future 객체에 대해 get이나 wait가 호출될 때까지 실행이 지연된다.
  • std::async의 기본 방침은 위 두 가지를 or로 결합한 것이다.
    • 결과적으로 기본 방침으로는 함수의 실행이 비동기일수도 동기일수도 있다.
// 아래 둘은 동일하다.
auto fut1 = std::async(f);
auto fut2 = std::async(std::launch::async | std::launch::deferred, f);

 

 

 

std::async를 기본 시동 방침으로 호출한 영향
// 아래 문장이 스레드 t에서 실행된다고 가정한다면
auto fut = std::async(f);
  • f가 지연 실행될 수 있으므로 f와 t가 동시에 실행될지 예측하기가 불가능하다.
  • f가 fut의 get, wait를 호출하는 스레드와 다른 스레드에서 실행될지 예측하기가 불가능하다.
  • get, wait를 호출한다는 보장이 없을 수 있으므로 f가 반드시 실행될 것인지 예측하기가 불가능할 수도 있다.

 

  • thread_local 변수(스레드 지역 변수)를 사용하기 힘들다.
    • 스레드 지역 저장소(thread-local storage, TLS)를 읽고 쓰는 코드가 f 내에 있다면
    • 그 코드가 어떤 스레드의 지역 변수에 접근할지 예측할 수 없기 때문이다.
  • 만료 시간이 있는 wait 기반 루프가 영원히 끝나지 않을 수 있다.
    • 지연되었다면, wait_for나 wait_until 호출 시 std::future_status::deffered가 리턴되기 때문이다.
using namespace std::literals;

void f()
{
    std::this_thread::sleep_for(ls); // 1초 sleep
}

// f를 비동기 실행
auto fut = std::async(f);

// f의 실행이 끝날 때까지 루프.
// 허나, 안 끝날 수가 있다!!
whild(fut.wait_for(100ms) != std::future_status::ready)
{
    ...
}
  • 해결 방법 : 지연 여부를 확인하여 지연 시 루프에 진입하지 않도록 처리하면 될 것이다.
    • 하지만, 안타깝게도 지연 여부를 future 객체로 직접 알아낼 방법이 없다.
    • wait_for 같은 시간 만료 기반 함수를 호출할 수밖에 없다.
auto fut = std::async(f);

// 지연 되었는지 체크
if(fut.wait_for(0s) == std::future_status::deffered)
{
    // f를 동기로 호출 처리
    ...
}
else
{
    // 지연되지 않았을 시
    while(fut.wait_for(100ms != std::future_status::ready)
    {
        ...
    }
    ...
}

 

 

 

기본 시동 방침과 함께 std::async를 사용하려면 지켜야 할 조건
  • get, wait를 호출하는 스레드와 반드시 동기적일 필요가 없다.
  • 여러 스레드 중 어떤 스레드의 thread_local 변수들을 읽고 쓰는지가 중요하지 않다.
  • std::async가 돌려준 future 객체에 get, wait가 반드시 호출된다는 보장이 없어도 된다.
  • std::async를 통한 함수가 전혀 실행되지 않아도 된다.
  • 함수 실행이 지연된 항태일 수도 있다는 점이 wait_for나 wait_until을 사용하는 코드에 반영되어 있다.

 

 

 

std::async가 반드시 비동기적으로 실행되도록 하는 방법
  • launch::async를 첫 인수로 지정한다.
auto = fut = std::async(std::launch::async, f); // f를 비동기 실행
  • std::launch::async 시동 방침을 명시적으로 지정하지 않는 함수를 만들어보자.
// C++11 버전
template<typename F, typename... Ts>
inline std::future<typename std::result_of<F(Ts...)>::type> 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)...);
}

 

728x90
Comments