第 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

For example:

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, ...)

Among them:

  • 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()

时,由调用这些函数的线程执行。

For example:

#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 会怎样

Yes, you can write:

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();

For example:

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

Example:

#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。

Example:

#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”错误状态。

For example:

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 用来把一个可调用对象包装成:

执行后自动把返回值或异常写进共享状态的任务。

For example:

#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)>

Indicates:

接收两个 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。

For example:

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 一定创建新线程。
  1. 对同一个普通 future 重复调用 get()
  1. 忘记处理异步任务在 get() 时重新抛出的异常。
  1. promise 销毁前既不 set_value() 也不 set_exception(),产生 broken promise。
  1. 把 move-only 的 promise / packaged_task 当成可复制对象使用。
  1. 为一个长期运行、需要明确停止控制的后台线程强行使用 std::async

Summary

  • std::future<T> 表示未来产生的 T 结果。
  • std::async 适合直接提交一个会返回结果的任务。
  • 明确要求真正异步执行时,使用 std::launch::async
  • future::get() 会等待结果并取得值,也会重新抛出异步任务中的异常。
  • std::promise 是结果通道的主动写入端。
  • std::packaged_task 把可调用对象包装成能产生 future 的任务。
  • std::shared_future 允许多个消费者读取同一个结果。
  • thread 偏线程生命周期管理,future/async 偏任务与结果管理。
音乐页