跳到正文

Wiki

future、async、promise 與 packaged_task

約 8 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

std::thread 很適合表達:

启动一个线程

在线程里执行某个函数

但它本身並不直接解決:

线程函数怎么把返回值交回来?

线程里抛出的异常怎么传回来?

主线程怎么知道结果准备好了?

多个线程之间怎么建立“结果通道”?

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

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

它們的核心思想可以概括成一句話:

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

所以:

std::thread
→ 更关心“线程本身”

std::future / std::async / std::promise
→ 更关心“任务最终产生什么结果”

1.future 和 async 基礎

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

std::future<T> 可以理解成:

一個“未來結果”的讀取端。

例如:

std::future<int>

表示:

未来某个时刻
会有一个 int 结果

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

std::future<void>

1.2.future 背後其實有一個 shared state

future 本身並不是結果,也不是執行緒。

它關聯的是一個:

共享狀態(shared state)

可以理解成:

生产者
写入结果 / 异常

 shared state

   future
读取结果 / 异常

共享狀態裡可能儲存:

任务返回值

任务抛出的异常

结果是否已经准备好

等待和同步所需的信息

所以:

future.get();

真正做的事情可以理解成:

如果结果还没准备好
→ 等待

结果准备好以后
→ 取出结果

如果任务保存的是异常
→ 在这里重新抛出

1.3.最簡單的 std::async

來看一個最簡單的例子:

#include <future>
#include <print>

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

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

    std::println("main continues");

    std::println("{}", result.get());
}

可能輸出:

main continues
30

這裡:

std::async(
    std::launch::async,
    calculate,
    10,
    20);

可以理解成:

提交 calculate(10, 20) 这个任务

让它异步执行

返回一个 future<int>

這個:

std::future<int> result

就是未來結果的讀取端。

主執行緒繼續執行:

std::println("main continues");

直到:

result.get();

如果任務已經完成:

直接取得结果

如果任務還沒完成:

当前线程在这里等待

所以:

std::async 負責“提交任務”,std::future 負責“以後拿結果”。


1.4.async 的引數和 thread 很像

基本形式:

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

例如:

auto result = std::async(
    std::launch::async,
    calculate,
    10,
    20);

其中:

policy
→ 任务怎么启动

callable
→ 要执行的函数 / lambda / 函数对象

arg1, arg2...
→ 传给这个可调用对象的参数

可呼叫物件可以是:

普通函数

lambda

函数对象

成员函数

如果需要傳引用,同樣需要考慮:

std::ref(x)

以及引用物件的生命週期問題。


2.async 啟動策略

2.1.std::launch::async

std::async 最常用的啟動策略之一是:

std::launch::async

例如:

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

它表示:

要求這個任務非同步執行。

可以先直觀理解成:

现在把任务安排出去执行

当前线程继续干自己的事

以后通过 future 等结果

重點是:

调用方关心的是任务和结果
而不是手动管理 thread 对象

所以它和:

std::thread

的使用視角不一樣。

std::thread 更像:

我要创建并管理一个线程

std::async 更像:

我要提交一个任务
最后拿它的结果

2.2.std::launch::deferred

另一個啟動策略是:

std::launch::deferred

例如:

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

它並不會立刻把任務放到後臺執行。

它表示:

先把這個任務記下來,等真正有人等待結果時再執行。

例如:

#include <future>
#include <print>
#include <thread>

int task()
{
    std::println(
        "task thread: {}",
        std::this_thread::get_id());

    return 42;
}

int main()
{
    std::println(
        "main thread: {}",
        std::this_thread::get_id());

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

    std::println("before get");

    std::println("{}", result.get());
}

可以理解成:

创建 async

task 此时还没执行

main 继续运行

执行 result.get()

就在调用 get() 的线程中执行 task()

得到结果

所以 deferred 並不是:

后台线程晚一点执行

而更接近:

延迟到真正需要结果时
再由等待结果的线程执行

2.3.asyncdeferred 的區別

可以簡單記成:

std::launch::async

→ 任务异步执行
→ 当前线程可以继续
→ future 以后取结果

而:

std::launch::deferred

→ 任务先不执行
→ 第一次 get()/wait() 时才执行
→ 由调用等待函数的线程执行

所以:

async
≈ “现在就安排执行”

deferred
≈ “先记下来,用到结果时再执行”

2.4.不寫 launch policy 會怎樣

可以這樣寫:

auto result = std::async(task);

但這種情況下,標準允許實現自己選擇:

async

或者

deferred

所以不要把:

std::async(task);

自動理解成:

一定立刻创建后台线程并发执行

如果你明確要求:

我就是想讓任務真正非同步執行。

那最好顯式寫:

std::launch::async

例如:

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

3.future 等待與異常

3.1.future::get()

get()future 最重要的函式之一:

auto value = future.get();

它做兩件事:

1. 等待结果准备好

2. 取得结果

例如:

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

int value = result.get();

如果任務還沒執行完:

get()
→ 等

任務結束以後:

get()
→ 返回 42

3.2.普通 future::get() 通常只能呼叫一次

例如:

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

int value = result.get();

這裡:

result.get();

把這個 future 裡的結果取走了。

之後:

result.valid()

通常會變成:

false

所以普通 std::future 可以理解成:

一次性的取件憑證。

结果准备好

get()

结果被取走

future 不再关联有效共享状态

因此不要這樣寫:

result.get();
result.get();

第二次 get() 會對無效 future 操作,通常會丟擲:

std::future_error

3.3.future::valid()

可以通過:

future.valid()

查詢:

這個 future 當前是否還關聯著有效的共享狀態。

例如:

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

std::println("{}", result.valid());

result.get();

std::println("{}", result.valid());

可以理解成:

get() 前
→ future 还关联共享状态

get() 后
→ 普通 future 的结果已经被消费

所以:

valid()

不是:

“任务成功了吗?”

而是:

“这个 future 现在还有没有关联一个有效共享状态?”

3.4.future::wait()

如果你只想:

等任務結束,但暫時不取結果。

可以:

future.wait();

例如:

result.wait();

std::println("task finished");

int value = result.get();

這裡:

wait()

只負責等待。

它不會把結果取走。

所以之後仍然可以:

result.get();

可以記成:

wait()
→ 等待,但不消费结果

get()
→ 等待 + 取走结果

3.5.wait() 不會把任務異常直接丟擲來

假設非同步任務丟擲了異常。

呼叫:

future.wait();

只是等待共享狀態變為 ready。

它不會在這裡重新丟擲任務異常。

異常通常是在:

future.get();

時重新丟擲。

所以:

wait()
→ 确认任务已经结束

get()
→ 真正取得“结果或者异常”

3.6.future::wait_for()

可以等待一段時間:

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

返回值可能是:

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

分別表示:

ready
→ 结果已经准备好了

timeout
→ 等待时间到了,但结果还没准备好

deferred
→ 这个任务使用 deferred 策略

示例:

#include <chrono>
#include <future>
#include <print>
#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::println("not ready yet");
    }

    std::println("{}", result.get());
}

輸出:

not ready yet
42

整個過程是:

任务开始

main 最多等 100ms

任务还没完成

wait_for() 返回 timeout

main 继续

最后 get() 等待并取得结果

3.7.future_status::deferred

如果:

wait_for(...)

返回:

std::future_status::deferred

說明:

這個任務根本不是正在後臺慢慢執行。

它只是一個延遲任務。

例如:

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

此時不斷寫:

result.wait_for(100ms);
result.wait_for(100ms);
result.wait_for(100ms);

並不會讓它“等著等著就在後臺完成”。

因為 deferred 的邏輯是:

真正执行 get()/wait()

调用这些函数的线程亲自执行任务

3.8.非同步任務裡的異常會通過 future 傳回來

這是 future 很重要的能力。

例如:

#include <future>
#include <print>
#include <stdexcept>

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

    try
    {
        std::println("{}", result.get());
    }
    catch (const std::exception& e)
    {
        std::println("{}", e.what());
    }
}

輸出:

task failed

這裡任務執行緒丟擲的:

std::runtime_error

不會直接從工作執行緒“跳到”主執行緒。

實際邏輯是:

异步任务抛异常

异常被保存到 shared state

main 调用 future.get()

get() 发现共享状态里保存的是异常

在 main 这里重新抛出

所以:

future.get();

取得的不一定是:

正常返回值

也可能是:

任务失败时保存的异常

這使得非同步任務的錯誤處理很像普通函式:

普通函数
→ 调用处 catch

异步任务
→ future.get() 处 catch

4.std::promise<T>

前面的:

std::async

會自動建立結果通道。

但有時候我們想自己控制:

什么时候写入结果

在哪个线程写入结果

写入正常值还是异常

這時可以使用:

std::promise<T>

它可以理解成:

future 對應結果通道的“寫入端”。

關係是:

promise<T>

   │ set_value()
   │ set_exception()

shared state

future<T>

   │ get()

读取结果

所以最簡單的記憶方式是:

promise
→ 写

future
→ 读

4.1.promise::get_future()

建立:

std::promise<int> promise;

以後,可以通過:

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

獲得和這個 promise 關聯的讀取端。

於是:

promise

future

通過同一個 shared state 連線起來。


4.2.promise 最簡單的例子

#include <future>
#include <print>
#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 thread(
        producer,
        std::move(promise));

    std::println("{}", future.get());

    thread.join();
}

輸出:

42

整個過程可以理解成:

main 创建 promise

main 从 promise 拿到 future

main 把 promise 移动给 worker

worker 调用 set_value(42)

42 被写入 shared state

main 的 future.get()

读出 42

4.3.為什麼 promise 要 std::move

std::promise 不能隨意複製。

它是:

move-only

所以:

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

是在把 promise 的所有權移動給工作執行緒。

可以理解成:

main
→ 保留 future,负责读

worker
→ 拿走 promise,负责写

這樣:

worker 写结果

shared state

main 读结果

就建立起來了。


4.4.promise::set_value()

生產端可以:

promise.set_value(42);

它表示:

把最終結果寫入共享狀態。

寫入以後:

future
→ ready

等待中的:

future.get();

就可以繼續執行。

對於:

std::promise<void>

沒有具體返回值。

可以:

promise.set_value();

表示:

任務已經成功完成。


4.5.promise 的結果通常只能設定一次

例如:

promise.set_value(42);
promise.set_value(100);

是不對的。

同一個共享狀態只能完成一次。

第一次:

set_value(42);

已經把結果確定了。

第二次再次:

set_value(...)

通常會拋:

std::future_error

所以可以理解成:

一个 promise
→ 最终只能提交一次最终结果

4.6.promise::set_exception()

promise 不僅能寫正常值:

promise.set_value(result);

也可以寫異常:

promise.set_exception(
    std::current_exception());

常見寫法:

void producer(std::promise<int> promise)
{
    try
    {
        int result = do_work();

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

這樣 consumer:

future.get();

會:

任务成功
→ 返回结果

任务失败
→ 重新抛出保存的异常

4.7.完整的 promise 異常示例

#include <exception>
#include <future>
#include <print>
#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 thread(
        producer,
        std::move(promise));

    try
    {
        std::println("{}", future.get());
    }
    catch (const std::exception& e)
    {
        std::println("{}", e.what());
    }

    thread.join();
}

輸出:

failed

流程是:

worker 发生异常

catch 捕获异常

promise.set_exception(...)

异常进入 shared state

main 调用 future.get()

异常在 main 中重新抛出

4.8.什麼是 broken promise

假設:

std::future<int> future;

{
    std::promise<int> promise;

    future = promise.get_future();
}

這裡 promise 離開作用域時:

没有 set_value()

也没有 set_exception()

但 future 還在等待結果。

這種情況叫:

broken promise

也就是:

原本负责生产结果的人已经消失

但结果还没有产生

標準庫不會讓 future 永遠傻等。

之後:

future.get();

會得到一個對應的:

std::future_error

可以把它理解成:

“結果生產端已經斷了,不會再有正常結果了。”


4.9.promise 的所有結束路徑都應該有明確結果

如果使用:

std::promise

生產端最好認真處理:

成功
→ set_value()

失败
→ set_exception()

提前退出 / 取消
→ 也要设计清楚最终结果

否則就容易出現:

broken promise

5.std::packaged_task

std::packaged_task 解決的是另一類問題。

假設已經有:

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

現在想把它變成:

一个“执行以后自动把结果写到 future”的任务

可以使用:

std::packaged_task

它可以理解成:

把一個可呼叫物件包裝成一個“自帶 future 結果通道”的任務物件。


5.1.packaged_task 最簡單的例子

#include <future>
#include <print>

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::println("{}", result.get());
}

輸出:

30

這裡:

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

表示:

这个任务:

接收两个 int
返回一个 int

建立:

std::packaged_task<int(int, int)> task(add);

以後:

task(10, 20);

實際上會:

调用 add(10, 20)

得到返回值 30

自动把 30 写入 shared state

result.get() 取得 30

5.2.packaged_task 和 promise 最大的區別

promise 是:

你自己決定什麼時候寫結果。

例如:

promise.set_value(42);

packaged_task 是:

你給它一個函式,執行這個函式時,它自動把函式的返回值寫入結果通道。

所以:

promise
→ 手动写结果

packaged_task
→ 执行函数,自动写结果

5.3.packaged_task 會自動傳遞異常

假設被包裝的函式:

int work()
{
    throw std::runtime_error("failed");
}

包裝成:

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

執行:

task();

如果 work() 拋異常,packaged_task 會把異常儲存到共享狀態。

之後:

future.get();

會重新丟擲這個異常。

所以它不只會自動處理:

返回值

也會自動處理:

异常

5.4.packaged_task 和 thread 配合

可以把 packaged task 交給執行緒執行:

#include <future>
#include <print>
#include <thread>

int calculate()
{
    return 42;
}

int main()
{
    std::packaged_task<int()> task(calculate);

    std::future<int> result =
        task.get_future();

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

    std::println("{}", result.get());

    thread.join();
}

輸出:

42

這裡:

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

表示:

把 task 移动给新线程

新线程执行 task()

task 内部调用 calculate()

返回值写入 shared state

result.get() 得到 42

5.5.packaged_task 也是 move-only

promise 類似:

std::packaged_task

也不能隨意複製。

通常需要:

std::move(task)

把它移動到:

线程

任务队列

worker

中。


5.6.為什麼 packaged_task 很適合任務佇列

執行緒池通常會有這種思想:

用户提交函数

包装成任务对象

放进任务队列

worker 线程取任务

执行任务

结果写进 shared state

用户拿 future 等结果

這正好和:

std::packaged_task

的設計非常契合。

因為 packaged task 本身已經把:

怎么执行任务
+
结果写到哪里

繫結在一起了。


5.7.promisepackaged_task 怎麼選

可以這樣記:

5.7.1.promise

適合:

我自己决定结果什么时候产生

结果不一定直接来自某个函数返回值

生产过程可能分很多步骤

例如:

promise.set_value(result);

由你自己控制。


5.7.2.packaged_task

適合:

我已经有一个函数 / lambda

我就想把它包装成一个“执行后有 future 结果”的任务

例如:

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

然後:

task();

自動把返回值送進 future。

所以最簡單的區別是:

promise
→ 手动生产结果

packaged_task
→ 包装可调用对象,自动生产结果

6.std::shared_future

普通:

std::future<T>

更適合:

一个消费者

因為:

future.get();

通常只能消費一次。

如果:

多個執行緒都需要讀取同一個非同步結果

可以使用:

std::shared_future<T>

6.1.future 轉成 shared_future

例如:

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

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

之後可以:

shared.get();
shared.get();
shared.get();

多個地方都可以讀取同一個結果。


6.2.shared_future 更像“共享檢視”

普通 future:

get()
→ 把结果取走
→ future 失效

而 shared future:

get()
→ 查看共享结果
→ 不会因为这次 get() 就把结果消费掉

所以:

future
→ 单消费者

shared_future
→ 多消费者

例如:

一次性加载配置

得到 shared_future<Config>

多个线程等待同一份配置

全部读取同一个结果

7.關係與選擇

7.1.這幾個東西到底是什麼關係

可以把整個 <future> 體系理解成:

                 shared state

       ┌─────────────┴─────────────┐
       │                           │
     写入端                      读取端
       │                           │
       │                        future
       │                    shared_future

       ├─ promise
       │   → 手动写结果

       ├─ packaged_task
       │   → 执行函数后自动写结果

       └─ async
           → 提交任务并自动管理结果通道

其中:

future
→ 负责以后读取结果

promise
→ 负责手动写结果

packaged_task
→ 把函数包装成“执行后自动写结果”的任务

async
→ 最方便的高级接口,直接提交任务并得到 future

shared_future
→ 允许多个消费者读取同一个结果

7.2.asyncthread 怎麼選

如果你關注的是:

我需要一个长期存在的线程

线程里面一直循环

我要控制它什么时候停止

我要管理生命周期

更適合:

std::thread
std::jthread

例如:

串口接收线程

传感器采集线程

机器人后台 worker

长期循环任务

如果你關注的是:

我有一个任务

它会算出一个结果

我以后要取得这个结果

任务中的异常也要传回来

更適合:

std::async
std::future

例如:

并行计算一段结果

后台加载文件

异步计算某个值

7.3.async 不是執行緒池

不要把:

std::async

理解成:

C++ 标准线程池

它並不是標準執行緒池介面。

如果你需要:

固定数量 worker

不断提交大量小任务

任务队列

长期后台执行

可控停止

通常應該考慮:

线程池

std::jthread

专门的任务系统

更完整的异步框架

而不是不斷:

std::async(...)

8.常見錯誤

8.1.錯誤:認為 std::async() 一定建立新執行緒

例如:

std::async(task);

如果不指定 policy,標準允許選擇:

async

deferred

如果明確要求真正非同步:

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

8.2.錯誤:對普通 future 重複 get()

錯誤:

future.get();
future.get();

普通 future 的結果通常只能消費一次。


8.3.錯誤:忘記 get() 還會重新丟擲異常

例如:

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

result.get();

這裡不僅可能拿到返回值,也可能丟擲任務裡的異常。

所以必要時要:

try
{
    result.get();
}
catch (...)
{
}

8.4.錯誤:promise 沒寫結果就被銷燬

如果 promise:

既没有 set_value()

也没有 set_exception()

就消失了,future 會得到:

broken promise

8.5.錯誤:把 promise / packaged_task 當可複製物件

它們通常是:

move-only

跨執行緒轉移時經常需要:

std::move(...)

8.6.錯誤:用 async 做長期後臺迴圈

例如:

std::async(
    std::launch::async,
    [] {
        while (true)
        {
            ...
        }
    });

如果這個任務需要:

明确停止

长期运行

生命周期管理

通常:

std::jthread

更符合語義。


9.最後總結

這組工具最核心的區別可以記成:

std::future
→ “以后我要读结果”

std::promise
→ “我手动把结果写进去”

std::packaged_task
→ “我执行这个函数,结果自动写进去”

std::async
→ “帮我提交任务,并直接给我 future”

std::shared_future
→ “多个地方都要读同一个结果”

其中:

std::thread / std::jthread
→ 更偏线程生命周期

std::future / std::async
→ 更偏任务与结果

如果只記一張圖:

          生产结果

 promise / packaged_task / async

        shared state

   future / shared_future

          读取结果

這就是整個 <future> 體系最核心的邏輯。