第 22.19.5 節

future、async、promise 與 packaged_task

0瀏覽次數0訪問次數--跳出率--平均停留

std::thread 很適合表達“啓動一個線程”,但它本身不直接提供:

  • 線程函數返回值;
  • 異常自動傳回調用方;
  • 一個統一的“未來結果”對象。

C++11 在 <future> 中提供了一組更偏“任務”的併發工具:

std::future
std::async
std::promise
std::packaged_task
std::shared_future

它們的核心思想是:

某個結果現在還沒有,但未來會產生;調用方先拿到一個 future,之後再等待或取得結果。

1. std::future<T> 是什麼

std::future<T> 表示:

未来某个时刻会得到一个 T

例如:

std::future<int>

表示未來會產生一個 int

如果任務沒有返回值,則使用:

std::future<void>

future 最重要的成員函數包括:

get()
wait()
wait_for()
wait_until()
valid()

2. 最簡單的 std::async

#include <future>
#include <iostream>

int calculate(int a, int b)
{
    return a + b;
}

int main()
{
    std::future<int> result = std::async(
        std::launch::async,
        calculate,
        10,
        20);

    std::cout << "main continues\n";
    std::cout << result.get() << '\n';
}

這裏:

std::async(...)

啓動一個異步任務,並返回:

std::future<int>

調用:

result.get()

時,如果結果還沒準備好,當前線程會等待;準備好後取得返回值。

輸出可能是:

main continues
30

3. async 的參數和 thread 很像

概念上:

std::async(policy, callable, arg1, arg2, ...)

其中:

  • policy:啓動策略;
  • callable:要執行的可調用對象;
  • 後續參數:傳給該可調用對象。

可調用對象同樣可以是:

  • 普通函數;
  • Lambda;
  • 函數對象;
  • 成員函數。

4. std::launch::asyncstd::launch::deferred

std::async 支持兩種重要啓動策略。

4.1 std::launch::async

std::async(std::launch::async, task);

要求任務異步執行。

可以把它理解成:

現在就安排任務獨立執行。

4.2 std::launch::deferred

std::async(std::launch::deferred, task);

表示延遲執行。

任務不會立刻運行,而是在第一次:

future.get()
future.wait()

時,由調用這些函數的線程執行。

例如:

#include <future>
#include <iostream>
#include <thread>

int task()
{
    std::cout << "task thread: "
              << std::this_thread::get_id()
              << '\n';
    return 42;
}

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

    auto result = std::async(std::launch::deferred, task);

    std::cout << result.get() << '\n';
}

這裏 task() 會在調用 get() 的線程中執行。

5. 不寫 policy 會怎樣

可以寫:

auto result = std::async(task);

此時標準庫允許實現選擇:

async
或
deferred

因此,如果你明確要求任務真正異步執行,建議顯式寫:

std::launch::async

否則不要假設“不寫策略就一定創建新線程”。

6. future::get() 只能取得一次

普通 std::future 的結果通常只能 get() 一次:

auto result = std::async(std::launch::async, [] {
    return 42;
});

int value = result.get();
// result.get(); // 不应再次 get

第一次 get() 後,future 通常不再關聯共享狀態。

可以用:

result.valid()

檢查它是否仍然關聯有效狀態。

7. wait():只等待,不取結果

future.wait();

只等待任務完成,不消費結果。

之後仍然可以:

future.get();

例如:

result.wait();
std::cout << "ready\n";
std::cout << result.get() << '\n';

8. wait_for():等待一段時間

auto status = result.wait_for(std::chrono::milliseconds(100));

返回:

std::future_status::ready
std::future_status::timeout
std::future_status::deferred

示例:

#include <chrono>
#include <future>
#include <iostream>
#include <thread>

using namespace std::chrono_literals;

int main()
{
    auto result = std::async(std::launch::async, [] {
        std::this_thread::sleep_for(1s);
        return 42;
    });

    if (result.wait_for(100ms) == std::future_status::timeout)
    {
        std::cout << "not ready yet\n";
    }

    std::cout << result.get() << '\n';
}

9. 異常會通過 future 傳播

這是 future 相比裸 std::thread 很方便的一點。

如果異步任務拋異常:

#include <future>
#include <iostream>
#include <stdexcept>

int main()
{
    auto result = std::async(std::launch::async, []() -> int {
        throw std::runtime_error("task failed");
    });

    try
    {
        std::cout << result.get() << '\n';
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << '\n';
    }
}

異常會被保存到共享狀態中,然後在:

result.get()

時重新拋出。

這比讓工作線程自己想辦法把錯誤傳回主線程方便很多。

10. std::promise<T>

std::promise<T> 可以理解成 future 通道的“寫入端”。

對應關係:

promise<T>  ----写入结果---->  shared state  ----读取结果----> future<T>

通過:

promise.get_future()

取得與它關聯的 future。

示例:

#include <future>
#include <iostream>
#include <thread>

void producer(std::promise<int> promise)
{
    promise.set_value(42);
}

int main()
{
    std::promise<int> promise;
    std::future<int> future = promise.get_future();

    std::thread t(producer, std::move(promise));

    std::cout << future.get() << '\n';
    t.join();
}

注意 std::promise 不能隨意複製,因此這裏通過:

std::move(promise)

把 promise 的所有權移動到工作線程。

11. promise::set_value()

promise.set_value(value);

向共享狀態寫入結果。

對於 std::promise<void>

promise.set_value();

表示任務成功完成,但沒有具體返回值。

12. promise::set_exception()

promise 也可以主動寫入異常:

try
{
    ...
}
catch (...)
{
    promise.set_exception(std::current_exception());
}

之後 future 調用:

future.get()

會重新拋出這個異常。

完整示例:

#include <exception>
#include <future>
#include <iostream>
#include <stdexcept>
#include <thread>

void producer(std::promise<int> promise)
{
    try
    {
        throw std::runtime_error("failed");
    }
    catch (...)
    {
        promise.set_exception(std::current_exception());
    }
}

int main()
{
    std::promise<int> promise;
    auto future = promise.get_future();

    std::thread t(producer, std::move(promise));

    try
    {
        std::cout << future.get() << '\n';
    }
    catch (const std::exception& e)
    {
        std::cout << e.what() << '\n';
    }

    t.join();
}

13. 什麼是 broken promise

如果 promise 在既沒有:

set_value()

也沒有:

set_exception()

的情況下被銷燬,那麼對應 future 不會永遠傻等。

它會得到一個“broken promise”錯誤狀態。

例如:

std::future<int> future;

{
    std::promise<int> promise;
    future = promise.get_future();
} // promise 销毁,但没有写入结果

future.get(); // 抛出 std::future_error

14. std::packaged_task

std::packaged_task 用來把一個可調用對象包裝成:

執行後自動把返回值或異常寫進共享狀態的任務。

例如:

#include <future>
#include <iostream>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    std::packaged_task<int(int, int)> task(add);
    std::future<int> result = task.get_future();

    task(10, 20);

    std::cout << result.get() << '\n';
}

這裏:

std::packaged_task<int(int, int)>

表示:

接收两个 int
返回一个 int

15. packaged_task 和 thread 配合

#include <future>
#include <iostream>
#include <thread>

int calculate()
{
    return 42;
}

int main()
{
    std::packaged_task<int()> task(calculate);
    std::future<int> result = task.get_future();

    std::thread t(std::move(task));

    std::cout << result.get() << '\n';
    t.join();
}

packaged_task 本身是 move-only 的,因此傳入線程時需要:

std::move(task)

線程池內部經常會出現類似思想:

把任务包装起来
      ↓
放入任务队列
      ↓
worker thread 取出并执行
      ↓
future 接收结果

16. promisepackaged_task 的區別

promise 更像:

我自己決定什麼時候、在什麼邏輯裏把結果寫進去。

packaged_task 更像:

我已經有一個可調用對象,執行它時自動把返回值寫進 future。

例如:

promise
适合手动生产结果

packaged_task
适合包装已有函数/任务

17. std::shared_future

普通 std::future 更偏單消費者:

future.get()

通常只能消費一次。

如果多個地方都需要讀取同一個異步結果,可以使用:

std::shared_future<T>

可以由普通 future 轉換:

auto future = std::async(std::launch::async, [] {
    return 42;
});

std::shared_future<int> shared = future.share();

之後多個線程可以:

shared.get()

讀取同一個結果。

18. asyncthread 怎麼選

如果你關心的是:

启动一个长期运行线程
控制线程生命周期
线程循环
明确 join/detach

使用:

std::thread / std::jthread

如果你關心的是:

提交一个任务
未来得到返回值
自动传播异常

可以優先考慮:

std::async / std::future

它們表達的是不同層次的抽象。

19. 常見錯誤

  1. 不寫 launch policy,卻默認認為 std::async 一定創建新線程。
  2. 對同一個普通 future 重複調用 get()
  3. 忘記處理異步任務在 get() 時重新拋出的異常。
  4. promise 銷燬前既不 set_value() 也不 set_exception(),產生 broken promise。
  5. 把 move-only 的 promise / packaged_task 當成可複製對象使用。
  6. 為一個長期運行、需要明確停止控制的後台線程強行使用 std::async

小結

  • std::future<T> 表示未來產生的 T 結果。
  • std::async 適合直接提交一個會返回結果的任務。
  • 明確要求真正異步執行時,使用 std::launch::async
  • future::get() 會等待結果並取得值,也會重新拋出異步任務中的異常。
  • std::promise 是結果通道的主動寫入端。
  • std::packaged_task 把可調用對象包裝成能產生 future 的任務。
  • std::shared_future 允許多個消費者讀取同一個結果。
  • thread 偏線程生命週期管理,future/async 偏任務與結果管理。
音乐页