본문 바로가기

비동기 프로그래밍

std::async·future·promise·packaged_task로 작업 실행과 결과 수신을 분리하고 비동기 오류를 전달합니다.

현대 C++는 스레드 관리의 복잡성을 추상화하여 더 쉽고 안전하게 동시성을 활용할 수 있도록 비동기 프로그래밍(Asynchronous Programming) 기능을 제공합니다.

비동기 프로그래밍은 작업을 백그라운드에서 실행하고, 결과가 준비되면 알림을 받는 방식으로, 주 스레드가 블록되지 않고 다른 작업을 계속 수행할 수 있게 합니다.

이번 장에서는 C++의 표준 비동기 프로그래밍 도구인 std::async, std::future, std::promise를 다룹니다.

비동기 도구는 “작업 시작”과 “결과 수신”의 책임 분리로 먼저 읽습니다.

이 절에서 “비동기 도구를 사용했다”와 “호출자와 별도 스레드에서 실제로 실행했다”를 구별합니다. 기본 std::async는 지연 실행을 선택할 수 있고, get()은 준비 전 결과를 기다리므로 호출자를 막을 수 있습니다. 먼저 다른 작업을 할 구간과 최종 결과를 반드시 기다릴 구간을 코드에서 정합니다. 실행 시간 측정 없이 세 도구의 사용만으로 속도 개선을 단정하지 않습니다.


비동기 프로그래밍의 필요성

일반적인 동기(Synchronous) 프로그래밍에서는 함수 호출이 완료될 때까지 호출자(caller) 스레드가 대기합니다.

이는 순차적인 작업에는 문제가 없지만, 시간이 오래 걸리는 작업(파일 I/O, 네트워크 통신, 복잡한 계산)의 경우 사용자 인터페이스가 멈추거나 전체 프로그램의 응답성이 저하될 수 있습니다.

비동기 프로그래밍의 장점
  • 응답성 향상: 긴 작업을 백그라운드에서 실행하여 주 스레드가 블록되지 않고 사용자 상호작용이나 다른 중요한 작업을 계속 처리할 수 있게 합니다.
  • 자원 활용 효율성: I/O 바운드(I/O-bound) 작업(CPU는 대기, I/O 장치 사용)과 CPU 바운드(CPU-bound) 작업(CPU 집중 사용)을 분리하여 시스템 자원을 더 효율적으로 사용할 수 있습니다.
  • 병렬성 활용: 여러 비동기 작업을 동시에 시작하여 다중 코어 CPU의 이점을 활용할 수 있습니다.

std::async와 std::future

C++11에서 도입된 std::async는 함수를 비동기적으로 실행하고 그 결과를 std::future 객체를 통해 나중에 비동기적으로 가져올 수 있게 해주는 템플릿 함수입니다.

이는 명시적으로 std::thread 객체를 생성하고 join() 또는 detach()하는 것보다 훨씬 고수준(high-level)의 추상화를 제공합니다.

  • std::async
    • 함수를 비동기적으로 실행합니다. 즉, async 정책에 따라 새 실행 스레드에서 실행하거나, deferred 정책으로 최초의 시간 제한 없는 대기 호출까지 지연시킬 수 있습니다. 표준 API가 사용자가 제어하는 스레드 풀을 제공하는 것은 아닙니다.
    • 실행 결과나 예외를 std::future 객체로 캡슐화하여 반환합니다.
    • std::launch 정책을 통해 실행 방식을 제어할 수 있습니다.
      • std::launch::async: 새로운 스레드를 생성하여 즉시 실행합니다.
      • std::launch::deferred: 함수 호출 시점이 아니라, std::future의 get() 또는 wait()가 호출될 때까지 실행을 지연시킵니다 (동기적으로 실행).
      • std::launch::async | std::launch::deferred (기본값): 컴파일러/런타임이 최적의 실행 방식을 선택하도록 합니다.
  • std::future
    • 비동기적으로 실행되는 함수의 결과(또는 예외)에 대한 핸들(handle) 역할을 합니다.
    • get() 멤버 함수를 호출하여 비동기 작업의 결과를 블로킹 방식으로 가져올 수 있습니다. 결과가 아직 준비되지 않았다면, get()은 준비될 때까지 호출 스레드를 블록합니다.
    • wait() 멤버 함수를 호출하여 결과가 준비될 때까지 블록하지만, 결과를 반환하지는 않습니다.
    • wait_for(), wait_until()을 사용하여 시간 제한을 두고 대기할 수 있습니다.
    • std::future는 move-only 타입입니다 (복사 불가, 이동 가능).
std::async와 std::future 사용 예시
#include <thread>
#include <iostream>
#include <utility>
#include <functional>
#include <future>   // std::async, std::future를 위해
#include <chrono>   // std::chrono::seconds를 위해
#include <string>   // std::string을 위해

// 시간이 오래 걸리는 계산 함수
int calculate_sum(int start, int end) {
    std::cout << "Calculating sum from " << start << " to " << end << " in thread: "
              << std::this_thread::get_id() << std::endl;
    int sum = 0;
    for (int i = start; i <= end; ++i) {
        sum += i;
        std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 작업 시뮬레이션
    }
    std::cout << "Calculation for " << start << "-" << end << " finished.\n";
    return sum;
}

int main() {
    std::cout << "Main thread started (ID: " << std::this_thread::get_id() << ")\n";

    // std::async를 사용하여 calculate_sum 함수를 비동기적으로 실행
    // 기본 정책 (std::launch::async | std::launch::deferred)
    std::future<int> future_sum1 = std::async(calculate_sum, 1, 500);

    // 명시적으로 새로운 스레드에서 실행하도록 지정
    std::future<int> future_sum2 = std::async(std::launch::async, calculate_sum, 501, 1000);

    std::cout << "Main thread continues doing other work...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1)); // 메인 스레드에서 다른 작업 수행

    // future_sum1의 결과를 기다리고 가져옴
    std::cout << "Waiting for future_sum1 result...\n";
    int result1 = future_sum1.get(); // get()은 결과를 받을 때까지 블록
    std::cout << "Result 1: " << result1 << std::endl;

    // future_sum2의 결과를 기다리고 가져옴
    std::cout << "Waiting for future_sum2 result...\n";
    int result2 = future_sum2.get();
    std::cout << "Result 2: " << result2 << std::endl;

    std::cout << "Total sum: " << result1 + result2 << std::endl;
    std::cout << "Main thread finished.\n";

    // 중요: future 객체가 소멸될 때 (예: 스코프를 벗어날 때),
    // async 정책으로 시작된 미완료 작업의 shared state에 대한 마지막 참조를 놓으면
    // 작업 완료를 기다릴 수 있습니다. 모든 future 소멸자가 대기하는 것은 아닙니다.
    // 따라서 명시적으로 get()이나 wait()를 호출하여 제어하는 것이 좋습니다.

    return 0;
}

std::async는 내부적으로 스레드를 관리하거나 지연 실행을 선택함으로써, 개발자가 직접 std::thread를 관리하는 복잡성을 줄여줍니다.

std::future는 비동기 작업의 결과를 조회하는 인터페이스를 제공합니다.


지연 실행은 언제 시작되는가?

앞의 긴 계산 예제에서 1..500의 합은 125250, 501..1000의 합은 375250, 합친 결과는 500500입니다. 각 계산의 로그 순서와 걸린 시간은 고정하지 않습니다. 기본 정책의 첫 작업이 deferred로 선택되면 첫 get을 호출한 스레드에서 그때 계산합니다.

다음 완전한 프로그램은 시간을 재는 대신 호출 횟수로 deferred 계약을 확인합니다. wait_for는 준비 여부를 검사하지만 이 지연 작업을 시작하지 않습니다.

deferred의 시작 시점과 future 소비
#include <chrono>
#include <future>
#include <iostream>

int main() {
    int calls = 0;
    auto result = std::async(std::launch::deferred, [&calls] {
        ++calls;
        return 42;
    });
    std::cout << "before=" << calls << '\n';
    auto status = result.wait_for(std::chrono::milliseconds(0));
    std::cout << "deferred="
              << (status == std::future_status::deferred)
              << " calls=" << calls << '\n';
    result.wait();
    std::cout << "after wait=" << calls
              << " valid=" << result.valid() << '\n';
    int value = result.get();
    std::cout << "value=" << value
              << " valid=" << result.valid() << '\n';
}

호출 횟수는 0 → 0 → 1 → 1입니다. wait()는 실행과 준비를 기다리지만 값을 소비하지 않아 future는 아직 valid입니다. get()은 42를 받으며 그 future의 상태를 소비하므로 이후 valid는 false입니다. 같은 future에서 두 번째 get()을 호출하지 않습니다.

독립적으로 작업 책임 구별하기

위 callable이 42 대신 7을 반환하도록 바꾸면 호출 횟수와 valid의 변화도 달라질까요? 또 std::packaged_task<int()> task(...)를 만들고 future만 얻은 상태에서, 작업을 실행하는 코드는 아직 없다고 하겠습니다. future를 얻었다는 사실만으로 작업이 실행되거나 ready가 됐다고 판단할 수 있나요?

풀이 확인

반환값만 7로 바뀌고 호출 횟수와 valid 변화는 같습니다. packaged_task 생성과 get_future()는 실행이 아닙니다. 별도의 호출자·스레드·실행기가 task의 operator()를 호출해야 함수가 실행되어 결과 상태를 채웁니다. producer 역할의 promise도 스스로 스레드를 만들지 않으므로, 본문의 thread 생성 코드와 set_value를 따로 찾아야 합니다.

std::promise

promise와 future는 값을 만드는 쪽과 기다리는 쪽을 분리합니다.

다음 그림은 그 단방향 전달 흐름을 보여줍니다.

promise와 future는 shared state로 값과 예외를 전달한다

promise가 결과를 준비할 때까지 future가 대기하고, get 이후 future의 유효성이 바뀌는 실제 공유 상태 수명이다. 값·예외·broken promise는 서로 다른 완료 경로로 보존한다.

C++ · FUTURE RESULT CONTRACT

promise와 future가 같은 결과 상태를 연결한다

promise의 get_future()로 같은 shared state를 가리키는 future를 얻는다. 생산자 설정과 소비자 대기는 실행 시작 책임과 분리되어 있다.

promise, shared state, future의 결과 연결생산자가 promise로 값 또는 예외를 설정하면 shared state가 ready가 된다. 소비자 future.get은 준비까지 기다려 그 값 또는 예외를 한 번 관찰하고 이후 invalid가 된다. broken_promise는 준비되지 않은 promise 소멸의 별도 실패이며 정상 값 다음의 두 번째 결과가 아니다.
promise<T>생산자set_value(value)또는 set_exception
설정설정promise<T> · 생산자 · set_value(value) · 또는 set_exception → shared state · 값 또는 예외 보관 · 설정 성공 → ready
shared state값 또는 예외 보관설정 성공 → ready
관찰관찰shared state · 값 또는 예외 보관 · 설정 성공 → ready → future<T> · 소비자 get() · 준비까지 대기 · 한 번 관찰 → invalid
future<T>소비자 get()준비까지 대기한 번 관찰 → invalid

promise · 생산자

set_value(value) 또는 set_exception(ptr)로 결과를 설정한다.

shared state · ready

설정이 성공하면 값 또는 예외를 보관한 shared state가 ready가 된다.

future · 한 번 관찰

get()은 준비까지 기다린 뒤 값 또는 저장된 예외를 관찰한다. 이후 future는 invalid가 된다.

본문 값 전달

prepare_data가 문자열을 설정하고 consume_data가 get으로 받는다.

prom.set_value(data);
// 다른 스레드
received_data = fut.get();

생산자와 소비자가 먼저 실행되는 순서는 달라도 shared state가 결과 준비를 연결한다.

대안 실패 · broken_promise

shared state를 가진 promise가 ready로 만들기 전에 소멸하면 실패 결과를 저장한다.

future.get은 이 저장된 예외를 소비자 위치에서 던진다. 값 수신 다음에 두 번째 결과가 오는 과정이 아니다.

wait()는 준비만 기다리고 값을 소비하지 않는다. future::get()은 값 또는 예외를 한 번 관찰하며 이후 같은 future로 다시 get하지 않는다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
원 도표의 제목·노드·관계·조건·주석
범위원문
본문·주석생산자와 소비자 중 누가 먼저 실행되는지는 달라도 shared state가 결과 준비를 연결한다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
공유 상태 대기

future가 아직 준비되지 않은 결과를 기다린다.

  • 공유 상태: 미준비
  • future: valid
  • 생산 promise가 완료 상태를 남길 때까지 값이 없다.
promise 완료

선택한 정상 사례에서 결괏값을 저장한다.

  • 공유 상태: 값 준비됨
  • future: get 가능
  • 값 또는 예외로 완료될 수 있으나 이 사례는 값 경로다.
get 소비

값을 꺼낸 뒤 future의 연결이 무효가 된다.

  • get: 값 반환
  • future: invalid
  • 일반 future의 get은 결과를 소비한다; shared_future와 구분한다.

promise와 future는 shared state로 값과 예외를 전달한다

promise가 결과를 준비할 때까지 future가 대기하고, get 이후 future의 유효성이 바뀌는 실제 공유 상태 수명이다. 값·예외·broken promise는 서로 다른 완료 경로로 보존한다.

C++ · FUTURE RESULT CONTRACT

promise와 future가 같은 결과 상태를 연결한다

promise의 get_future()로 같은 shared state를 가리키는 future를 얻는다. 생산자 설정과 소비자 대기는 실행 시작 책임과 분리되어 있다.

promise, shared state, future의 결과 연결생산자가 promise로 값 또는 예외를 설정하면 shared state가 ready가 된다. 소비자 future.get은 준비까지 기다려 그 값 또는 예외를 한 번 관찰하고 이후 invalid가 된다. broken_promise는 준비되지 않은 promise 소멸의 별도 실패이며 정상 값 다음의 두 번째 결과가 아니다.
promise<T>생산자set_value(value)또는 set_exception
설정설정promise<T> · 생산자 · set_value(value) · 또는 set_exception → shared state · 값 또는 예외 보관 · 설정 성공 → ready
shared state값 또는 예외 보관설정 성공 → ready
관찰관찰shared state · 값 또는 예외 보관 · 설정 성공 → ready → future<T> · 소비자 get() · 준비까지 대기 · 한 번 관찰 → invalid
future<T>소비자 get()준비까지 대기한 번 관찰 → invalid

promise · 생산자

set_value(value) 또는 set_exception(ptr)로 결과를 설정한다.

shared state · ready

설정이 성공하면 값 또는 예외를 보관한 shared state가 ready가 된다.

future · 한 번 관찰

get()은 준비까지 기다린 뒤 값 또는 저장된 예외를 관찰한다. 이후 future는 invalid가 된다.

본문 값 전달

prepare_data가 문자열을 설정하고 consume_data가 get으로 받는다.

prom.set_value(data);
// 다른 스레드
received_data = fut.get();

생산자와 소비자가 먼저 실행되는 순서는 달라도 shared state가 결과 준비를 연결한다.

대안 실패 · broken_promise

shared state를 가진 promise가 ready로 만들기 전에 소멸하면 실패 결과를 저장한다.

future.get은 이 저장된 예외를 소비자 위치에서 던진다. 값 수신 다음에 두 번째 결과가 오는 과정이 아니다.

wait()는 준비만 기다리고 값을 소비하지 않는다. future::get()은 값 또는 예외를 한 번 관찰하며 이후 같은 future로 다시 get하지 않는다.

이 예의 조건

  • 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
  • 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.

관계도의 보충 설명

원 도표의 제목·노드·관계·조건·주석
범위원문
본문·주석생산자와 소비자 중 누가 먼저 실행되는지는 달라도 shared state가 결과 준비를 연결한다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
공유 상태 대기

future가 아직 준비되지 않은 결과를 기다린다.

  • 공유 상태: 미준비
  • future: valid
  • 생산 promise가 완료 상태를 남길 때까지 값이 없다.
promise 완료

선택한 정상 사례에서 결괏값을 저장한다.

  • 공유 상태: 값 준비됨
  • future: get 가능
  • 값 또는 예외로 완료될 수 있으나 이 사례는 값 경로다.
get 소비

값을 꺼낸 뒤 future의 연결이 무효가 된다.

  • get: 값 반환
  • future: invalid
  • 일반 future의 get은 결과를 소비한다; shared_future와 구분한다.

std::promise는 std::future와 함께 사용되어, 한 스레드에서 결과를 약속(promise)하고, 다른 스레드에서 이 결과를 기다리는(future) 패턴을 구현합니다.

std::promise는 std::future 객체와 연결되어 있으며, set_value(), set_exception() 등의 함수를 통해 future에 값을 설정하거나 예외를 전달할 수 있습니다.

  • std::promise
    • 어떤 값을 나중에 제공하겠다는 약속을 나타냅니다.
    • get_future()를 호출하여 자신과 연결된 std::future 객체를 얻습니다.
    • set_value(value): 연결된 future에 값을 설정합니다.
    • set_exception(exception_ptr): 연결된 future에 예외를 설정합니다.
std::promise와 std::future를 이용한 스레드 간 통신
#include <iostream>
#include <thread>
#include <utility>
#include <functional>
#include <future>   // std::promise, std::future를 위해
#include <string>
#include <chrono>

// 데이터를 준비하고 promise를 통해 전달하는 함수
void prepare_data(std::promise<std::string> prom) {
    std::cout << "Data preparer thread (ID: " << std::this_thread::get_id() << ") started.\n";
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 데이터 준비 시간 시뮬레이션

    std::string data = "Hello from async world!";
    std::cout << "Data preparer: Data prepared.\n";
    prom.set_value(data); // promise를 통해 값을 설정 (future로 전달됨)
    std::cout << "Data preparer: Value set in promise.\n";
}

// 데이터를 기다리고 future를 통해 받는 함수
void consume_data(std::future<std::string>& fut) {
    std::cout << "Data consumer thread (ID: " << std::this_thread::get_id() << ") started.\n";
    std::cout << "Data consumer: Waiting for data...\n";

    try {
        std::string received_data = fut.get(); // future의 get()을 통해 값 블로킹 대기
        std::cout << "Data consumer: Received data: '" << received_data << "'\n";
    } catch (const std::exception& e) {
        std::cerr << "Data consumer: Caught exception: " << e.what() << std::endl;
    }
}

int main() {
    std::cout << "Main thread started (ID: " << std::this_thread::get_id() << ")\n";

    // 1. promise 객체 생성
    std::promise<std::string> data_promise;

    // 2. promise에서 future 객체 얻기
    std::future<std::string> data_future = data_promise.get_future();

    // 3. producer 스레드는 promise를 받아 값을 설정
    std::thread producer_thread(prepare_data, std::move(data_promise)); // promise는 move-only

    // 4. consumer 스레드는 future를 받아 값을 기다림
    std::thread consumer_thread(consume_data, std::ref(data_future)); // future도 move-only이지만,
                                                                       // 여기서는 참조로 전달하여 소유권을 유지

    std::cout << "Main thread doing other tasks...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));

    producer_thread.join();
    consumer_thread.join();

    std::cout << "Main thread finished.\n";
    return 0;
}

이 예시에서 prepare_data 함수는 std::promise를 통해 data를 전달하고, consume_data 함수는 std::future를 통해 그 data를 받습니다.

이는 뮤텍스나 조건 변수를 직접 사용하지 않고도 스레드 간에 안전하게 데이터를 전달하는 방법입니다.


결과를 준비하지 않은 promise의 종료

생산자 쪽 promise가 값이나 예외를 설정하지 않은 채, 아직 준비되지 않은 결과 상태를 버리면 소비자는 무한히 정상 값을 기다리는 대신 broken_promise 오류를 받습니다. 다음은 스레드 없이 그 실패 경계를 드러내는 별도 완전한 프로그램입니다.

생산자가 ready로 만들기 전에 사라진 결과 상태
#include <future>
#include <iostream>

int main() {
    std::future<int> result;
    {
        std::promise<int> producer;
        result = producer.get_future();
    }
    try {
        std::cout << result.get() << '\n';
    } catch (const std::future_error& error) {
        std::cout << "broken="
                  << (error.code() == std::make_error_code(
                         std::future_errc::broken_promise)) << '\n';
    }
}

이 조건에서는 broken=1을 출력합니다. 반대로 promise가 소멸 전에 set_value(9)를 성공했다면 소비자는 저장된 값 9를 받습니다. set_exception을 성공했다면 get 위치에서 그 저장된 예외가 다시 던져집니다. 정상 값 뒤에 추가 오류가 배달되는 순서가 아니라, 한 결과 상태를 값 또는 예외로 준비하는 서로 다른 경로입니다.

std::packaged_task

std::packaged_task는 호출 가능한 객체(함수, 람다, 함수 객체)를 std::future와 연결하여 비동기적으로 실행할 수 있도록 래핑(wrap)하는 클래스 템플릿입니다.

std::async와 유사하지만, std::packaged_task는 스레드 생성 및 실행을 더욱 세밀하게 제어해야 할 때 유용합니다.

  • std::packaged_task
    • 생성자에서 실행할 함수(또는 람다 등)를 받습니다.
    • get_future()를 호출하여 연결된 std::future를 얻습니다.
    • 객체 자체가 함수 호출 연산자 ()를 오버로드하고 있어, 이를 호출하면 래핑된 함수가 실행되고 결과가 future에 저장됩니다.
std::packaged_task를 이용한 비동기 작업
#include <iostream>
#include <thread>
#include <utility>
#include <functional>
#include <future>   // std::packaged_task를 위해
#include <chrono>

int process_heavy_task(int value) {
    std::cout << "Processing heavy task with value " << value << " in thread: "
              << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(3));
    return value * 2;
}

int main() {
    std::cout << "Main thread started (ID: " << std::this_thread::get_id() << ")\n";

    // 1. packaged_task 객체 생성 (함수를 래핑)
    std::packaged_task<int(int)> task(process_heavy_task);

    // 2. task에서 future 객체 얻기
    std::future<int> result_future = task.get_future();

    // 3. task를 별도의 스레드에서 실행
    std::thread worker_thread(std::move(task), 10); // task는 move-only

    std::cout << "Main thread continues doing other things...\n";
    std::this_thread::sleep_for(std::chrono::seconds(1));

    // 4. 결과를 기다리고 가져옴
    std::cout << "Main thread: Waiting for task result...\n";
    int result = result_future.get();
    std::cout << "Main thread: Task result: " << result << std::endl;

    worker_thread.join(); // worker_thread가 종료될 때까지 대기

    std::cout << "Main thread finished.\n";
    return 0;
}

std::packaged_task는 std::async보다 더 세밀한 제어를 제공하며, 특히 스레드 풀과 같은 사용자 정의 동시성 프레임워크를 구축할 때 유용합니다.

std::async, promise, packaged_task는 모두 future를 중심으로 결과를 전달하지만, 시작 시점과 실행 제어권이 다릅니다.

C++ · ASYNC TOOL CHOICE

실행 시작과 결과 확정의 책임으로 도구를 고른다

세 도구 모두 future와 shared state를 연결하지만, 누가 callable을 시작하고 누가 결과를 ready로 만드는지는 다르다.

std::async · 실행까지 위임

callable의 실행 정책과 결과 연결을 함께 맡긴다.

std::async(calculate_sum,
  1, 500);

기본 정책은 async 또는 deferred이다. 별도 실행이 계약이면 std::launch::async를 명시한다.

std::promise · 생산자가 확정

실행은 별도로 관리하고 생산자가 값 또는 예외를 설정한다.

auto fut = prom.get_future();
prom.set_value(data);

본문 prepare_data 스레드는 promise를 move로 받고 consume_data 스레드는 future를 참조한다.

packaged_task · 호출 위치 제어

callable과 결과 상태를 묶고 선택한 실행기에서 task를 호출한다.

std::packaged_task<int(int)>
  task(process_heavy_task);

객체를 만들기만 하면 실행되지 않는다. 본문은 std::thread(std::move(task), 10)으로 실행한다.

대기 책임 · 호출자

get/wait가 막는 위치와 실행 자원 정리를 함께 설계한다.

result_future.get();
worker_thread.join();

deferred는 첫 get/wait에서 호출한 스레드로 실행된다. async 작업의 마지막 상태 참조 해제도 완료를 기다릴 수 있다.

promise와 packaged_task 자체는 새 스레드를 만들지 않는다. timed wait는 deferred 작업을 시작하지 않으며, 모든 future 소멸자가 대기하는 것도 아니다.


비동기 프로그래밍 정리

  • std::async: 가장 간단하게 비동기 작업을 시작하고 결과를 std::future로 받을 수 있는 고수준 함수입니다. (내부적으로 스레드 관리 또는 지연 실행)
  • std::promise: 한 스레드에서 다른 스레드로 결과(또는 예외)를 전달할 때 사용합니다. std::future와 함께 작동합니다.
  • std::packaged_task: 호출 가능한 객체를 std::future와 연결하여, 사용자 정의 스레드에서 실행할 때 유용합니다.

이러한 비동기 프로그래밍 도구들은 저수준 스레딩 API보다 훨씬 안전하고 생산적입니다.

개발자는 동기화 프리미티브의 복잡성을 직접 다루는 대신, 작업의 병렬성 및 비동기적 흐름에 집중할 수 있게 됩니다.

비동기 프로그래밍은 std::async, future, promise, packaged_task가 결과 전달 책임을 어떻게 나누는지로 읽습니다.