스레드 기반 위의 태스크 기반 프로그래밍
Source: Dev.to
스레드 기반보다 작업 기반 프로그래밍을 선호하세요
비동기로 무언가를 실행하고 싶다면 두 가지 기본 선택지가 있습니다: std::thread와 std::async.
int doAsyncWork();
std::thread th(doAsyncWork); // 스레드 기반
std::async 사용 (권장)
auto future = std::async(doAsyncWork);
std::async(doAsyncWork)는 **작업(task)**을 생성합니다.
작업이 더 우수한 이유는 std::future를 반환하는데, 이를 이용해 프로그램을 동기화할 수 있기 때문입니다. future는 결과를 가져오거나 작업 함수가 던진 예외를 다시 발생시킬 수 있는 get() 함수를 제공합니다. 작업은 또한 더 높은 수준의 추상화를 제공합니다.
스레드란 무엇인가?
- 하드웨어 스레드 – CPU가 제공하는 물리적 실행 단위. 최신 CPU는 코어당 하나 이상의 하드웨어 스레드를 제공할 수 있습니다.
- 소프트웨어 스레드(OS 또는 시스템 스레드) – 운영 체제가 관리하는 추상화이며, 하드웨어 스레드에 스케줄링됩니다.
std::thread객체는 기본 소프트웨어 스레드에 대한 핸들입니다.
소프트웨어 스레드는 제한된 자원입니다. 시스템이 제공할 수 있는 것보다 더 많은 스레드를 생성하면 std::system_error가 발생합니다(심지어 noexcept 함수에서도).
스레드 풀을 다 쓰지 않더라도 오버서브스크립션이 발생할 수 있습니다: 사용 가능한 하드웨어 스레드보다 더 많은 실행 준비가 된 소프트웨어 스레드가 존재하게 되면, 빈번한 컨텍스트 스위치와 오버헤드가 증가합니다.
왜 std::async를 선호해야 하는가?
std::async는 스레드 생성 및 스케줄링 관리를 구현에 위임합니다. std::async로 만든 작업은 새로운 소프트웨어 스레드가 보장되지 않기 때문에 오버서브스크립션 문제를 보통 피합니다. 대신, 스케줄러는 결과가 필요할 때 기존 스레드에서 함수를 실행할 수 있습니다.
기본 런치 정책을 사용할 경우, 구현은 비동기 실행(새 스레드)과 지연 실행(같은 스레드) 중 하나를 선택할 수 있습니다.
런치 정책
| 정책 | 동작 |
|---|---|
std::launch::async | 함수가 새로운 스레드에서 실행된다는 것이 보장됩니다. |
std::launch::deferred | 함수가 즉시 실행되지 않습니다. future.get() 또는 future.wait()가 호출될 때만 실행되며, 호출 스레드에서 실행됩니다. |
기본 (std::launch::async | std::launch::deferred) | 구현이 작업을 비동기적으로(새 스레드) 실행할지, 지연시켜(같은 스레드, 지연 실행) 실행할지를 결정합니다. |
적절한 런치 정책과 함께 std::async를 사용하면 스레드 수명 관리와 오버서브스크립션 문제를 수동으로 다루지 않아도 더 명확하고 안전한 비동기 코드를 작성할 수 있습니다.