thread_async
thread_async (비동기 작업 실행)
std::async는 함수를 비동기적으로 실행하고 그 결과를 나중에 얻을 수 있는 std::future를 반환하는 함수 템플릿이에요. 실행 정책에 따라 별도의 스레드에서 즉시 실행하거나, 결과가 요청될 때까지 지연시킬 수 있어요. 이 페이지에서는 std::async의 동작 방식과 정책, 예외 처리, 예제를 자세히 설명할게요.
출처: cppreference
본문
헤더 및 선언
<future> 헤더에 정의됨 |
||
|---|---|---|
template < class F , class ... Args > std :: future < /* see below */ > async ( F && f , Args && ... args ); |
(1) | (C++11부터) |
template < class F , class ... Args > std :: future < /* see below */ > async ( std :: launch policy , F && f , Args && ... args ); |
(2) | (C++11부터) |
함수 템플릿 std::async는 함수 f를 비동기적으로 실행하고(별도의 스레드에서 실행될 수 있으며, 이 스레드는 스레드 풀의 일부일 수도 있어요) 그 함수 호출의 결과를 최종적으로 보관하게 될 std::future를 반환해요.
std::async의 반환 타입은 std::future<V>인데, 여기서 V는 다음과 같아요:
typename std :: result_of < typename std :: decay < F >:: type ( typename std :: decay < Args >:: type ...) >:: type . |
(C++17까지) |
|---|---|
std :: invoke_result_t < std :: decay_t < F > , std :: decay_t < Args > ... > . |
(C++17부터) |
다음 조건 중 하나라도 만족하면 프로그램은 ill-formed예요:
F가 MoveConstructible이 아님. Args의 어떤 타입이 MoveConstructible이 아님. INVOKE ( decay-copy ( std :: forward < F > ( f )), decay-copy ( std :: forward < Args > ( args ))...)가 유효한 표현식이 아님. |
(C++20까지) |
|---|---|
다음 중 하나라도 거짓이면 프로그램은 ill-formed예요: std :: is_constructible_v < std :: decay_t < F > , F > ( std :: is_constructible_v < std :: decay_t < Args > , Args > && ...) std :: is_invocable_v < std :: decay_t < F > , std :: decay_t < Args > ... > |
(C++20부터) |
std::async 호출은 f 호출과 동기화되고, f의 완료는 공유 상태가 준비되기 전에 sequenced돼요.
매개변수
f |
- | 호출할 Callable 객체 |
|---|---|---|
args |
- | f에 전달할 매개변수 |
policy |
- | 실행 방법을 제어하는 비트마스크 값 |
반환값
이 std::async 호출로 생성된 공유 상태를 참조하는 std::future를 반환해요.
실행 정책
비동기 호출 (Async invocation)
async 플래그가 설정된 경우, 즉 ( policy & std :: launch :: async ) != 0이면 std::async는 다음을 std::thread 객체로 표현되는 새 실행 스레드에서 호출해요:
INVOKE ( decay-copy ( std :: forward < F > ( f )), decay-copy ( std :: forward < Args > ( args ))...) |
(C++23까지) |
|---|---|
std :: invoke ( auto ( std :: forward < F > ( f )), auto ( std :: forward < Args > ( args ))...) |
(C++23부터) |
decay-copy의 호출은 현재 스레드에서 평가돼요. |
(C++23까지) |
|---|---|
auto가 생성한 값은 현재 스레드에서 구체화돼요. |
(C++23부터) |
함수 f가 값을 반환하거나 예외를 던지면, 그 값이나 예외는 std::async가 호출자에게 반환하는 std::future를 통해 접근할 수 있는 공유 상태에 저장돼요.
지연 호출 (Deferred invocation)
deferred 플래그가 설정된 경우, 즉 ( policy & std :: launch :: deferred ) != 0이면 std::async는 다음을 공유 상태에 저장해요:
decay-copy ( std :: forward < F > ( f ))와 decay-copy ( std :: forward < Args > ( args ))...를 공유 상태에 저장. |
(C++23까지) |
|---|---|
auto ( std :: forward < F > ( f ))와 auto ( std :: forward < Args > ( args ))...를 공유 상태에 저장. |
(C++23부터) |
지연 평가는 다음과 같이 수행돼요:
std::async가 호출자에게 반환한std::future에 대해 시간 제한이 없는 대기 함수를 처음 호출하면, 그 대기 함수를 호출한 스레드(원래std::async를 호출한 스레드일 필요는 없어요)에서INVOKE ( std :: move ( g ), std :: move ( xyz ))를 평가해요. 여기서
g는 decay-copy ( std :: forward < F > ( f ))의 저장된 값이고, xyz는 decay-copy ( std :: forward < Args > ( args ))...의 저장된 복사본이에요. |
(C++23까지) |
|---|---|
g는 auto ( std :: forward < F > ( f ))의 저장된 값이고, xyz는 auto ( std :: forward < Args > ( args ))...의 저장된 복사본이에요. |
(C++23부터) |
- 결과나 예외는 반환된
std::future와 연관된 공유 상태에 저장되고, 그 후에야 준비 상태가 돼요. 이후 같은std::future에 대한 모든 접근은 즉시 결과를 반환해요.
기타 정책
policy에 std::launch::async도 std::launch::deferred도, 구현 정의 정책 플래그도 설정되어 있지 않으면 동작은 정의되지 않아요.
정책 선택
둘 이상의 플래그가 설정된 경우 어떤 정책이 선택될지는 구현 정의예요. 기본값(정책에 std::launch::async와 std::launch::deferred 플래그가 모두 설정된 경우)에 대해 표준은 사용 가능한 동시성을 활용하고 추가 작업은 지연시키는 것을 권장하지만(요구하지는 않아요) 명시하고 있어요.
std::launch::async 정책이 선택된 경우,
- 이
std::async호출이 생성한 공유 상태를 공유하는 비동기 반환 객체에 대한 대기 함수 호출은 연결된 스레드가 마치 join된 것처럼 완료될 때까지 차단되거나, 아니면 시간 초과돼요. - 연결된 스레드의 완료는 공유 상태를 기다리는 첫 번째 함수의 성공적인 반환 또는 공유 상태를 해제하는 마지막 함수의 반환 중 먼저 도래하는 것과 동기화돼요.
예외
다음과 같은 예외를 던져요:
- 내부 데이터 구조용 메모리를 할당할 수 없으면
std::bad_alloc policy == std :: launch :: async이고 구현이 새 스레드를 시작할 수 없으면 오류 조건std::errc::resource_unavailable_try_again을 가진std::system_error
policy가 std::launch::async | std::launch::deferred이거나 추가 비트가 설정된 경우에는 이 경우 지연 호출 또는 구현 정의 정책으로 대체돼요.
참고 사항
구현은 기본 실행 정책에 추가적인(구현 정의) 비트를 활성화하여 std::async의 첫 번째 오버로드 동작을 확장할 수 있어요.
구현 정의 실행 정책의 예로는 동기 정책(std::async 호출 내에서 즉시 실행)과 작업 정책(std::async와 유사하지만 스레드 로컬 변수가 지워지지 않음)이 있어요.
std::async에서 얻은 std::future를 이동하지 않거나 참조에 바인딩하지 않으면, std::future의 소멸자는 전체 표현식의 끝에서 비동기 작업이 완료될 때까지 차단해요. 따라서 다음과 같은 코드는 본질적으로 동기적으로 동작하게 돼요:
std::async(std::launch::async, []{ f(); }); // 임시 객체의 소멸자가 f()를 기다려요
std::async(std::launch::async, []{ g(); }); // f()가 완료될 때까지 시작하지 않아요
std::async 호출 이외의 방법으로 얻은 std::future의 소멸자는 절대 차단하지 않는다는 점에 유의하세요.
예제
#include <algorithm>
#include <future>
#include <iostream>
#include <mutex>
#include <numeric>
#include <string>
#include <vector>
std::mutex m;
struct X
{
void foo(int i, const std::string& str)
{
std::lock_guard<std::mutex> lk(m);
std::cout << str << ' ' << i << '\n';
}
void bar(const std::string& str)
{
std::lock_guard<std::mutex> lk(m);
std::cout << str << '\n';
}
int operator()(int i)
{
std::lock_guard<std::mutex> lk(m);
std::cout << i << '\n';
return i + 10;
}
};
template<typename RandomIt>
int parallel_sum(RandomIt beg, RandomIt end)
{
auto len = end - beg;
if (len < 1000)
return std::accumulate(beg, end, 0);
RandomIt mid = beg + len / 2;
auto handle = std::async(std::launch::async,
parallel_sum<RandomIt>, mid, end);
int sum = parallel_sum(beg, mid);
return sum + handle.get();
}
int main()
{
std::vector<int> v(10000, 1);
std::cout << "The sum is " << parallel_sum(v.begin(), v.end()) << '\n';
X x;
// 기본 정책으로 (&x)->foo(42, "Hello")를 호출해요:
// "Hello 42"를 동시에 출력하거나 실행을 지연할 수 있어요
auto a1 = std::async(&X::foo, &x, 42, "Hello");
// 지연 정책으로 x.bar("world!")를 호출해요
// a2.get() 또는 a2.wait()가 호출될 때 "world!"를 출력해요
auto a2 = std::async(std::launch::deferred, &X::bar, x, "world!");
// async 정책으로 X()(43)을 호출해요
// "43"을 동시에 출력해요
auto a3 = std::async(std::launch::async, X(), 43);
a2.wait(); // "world!"를 출력해요
std::cout << a3.get() << '\n'; // "53"을 출력해요
} // a1이 이 시점에 완료되지 않았다면, a1의 소멸자가 여기서 "Hello 42"를 출력해요
가능한 출력:
The sum is 10000
43
world!
53
Hello 42
결함 보고서
다음 동작 변경 결함 보고서는 이전에 발표된 C++ 표준에 소급 적용되었어요.
| DR | 적용 대상 | 발표된 동작 | 올바른 동작 |
|---|---|---|---|
| LWG 2021 | C++11 | 반환 타입이 부정확하고 지연 경우의 인수 값 범주가 불명확함 | 반환 타입을 수정하고 rvalue가 사용됨을 명확히 함 |
| LWG 2078 | C++11 | policy가 std::launch::async 외의 다른 실행 정책을 지정할 때 std::system_error가 발생할 수 있는지 불명확했음 |
policy == std :: launch :: async인 경우에만 발생할 수 있음 |
| LWG 2100 | C++11 | std::launch::async 정책을 사용할 때 시간 제한 대기 함수가 시간 초과할 수 없었음 |
허용함 |
| LWG 2120 | C++11 | 표준 또는 구현 정의 정책이 설정되지 않은 경우 동작이 불명확했음 | 이 경우 동작은 정의되지 않음 |
| LWG 2186 | C++11 | 지연 평가에서 반환된 값과 던져진 예외가 어떻게 처리되는지 불명확했음 | 공유 상태에 저장됨 |
| LWG 2752 | C++11 | 내부 데이터 구조용 메모리를 할당할 수 없을 때 std::async가 std::bad_alloc을 던지지 않을 수 있었음 |
던짐 |
| LWG 3476 | C++20 | (decay된 타입의) F와 인수 타입이 직접 move constructible이어야 했음 |
이 요구 사항을 제거함 [ 1 ] |
- ↑ move-constructibility는
std::is_constructible_v에 의해 이미 간접적으로 요구돼요.
같이 보기
future (C++11) |
비동기적으로 설정되는 값을 기다리는 클래스 템플릿 [편집] |
|---|---|
| C++ 문서: 실행 지원 라이브러리 |