【C++现代#5】C++11并发支持库 -- 异步 future

📃个人主页:island1314
⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞
- 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》
🔥 目录
异步任务和 Future (<future>)
std::future 提供了一种机制,允许你在一个线程中等待另一个线程计算的结果。
| 组件 | 描述 |
|---|---|
std::future |
用于访问异步操作结果的类模板。它充当异步计算结果的“代理”。 |
std::async |
用于异步执行一个可调用对象,它返回一个 std::future 对象。这是启动一个新任务并获取其结果最简单的方法。 |
std::promise |
用于在一个线程中设置一个值,供另一个线程通过关联的 std::future 来获取。 |
std::packaged_task |
封装一个可调用对象,并将其结果存储起来,以便通过 std::future 来获取。 |
1. 基本概述
1.1 核心概念
想象你在主线程启动了一个耗时的计算任务(比如在一个新线程中),std::future 就是主线程用来查询和接收这个任务结果的信封。
- 生产者 (Producer): 异步任务(在新线程中运行),负责计算结果并将结果或异常放入一个共享状态。
- 消费者 (Consumer):
std::future对象(在主线程中),负责访问共享状态中的结果。
共享状态 (Shared State)
std::future 对象不直接存储结果,而是引用一个共享状态。这个共享状态包含了:
- 任务的结果(如果任务有返回值)。
- 任务执行过程中可能抛出的异常。
- 任务是否已经完成的状态信息。
1.2 如何获得 std::future对象?
获得 std::future 对象通常有三种方式,它们代表了三种不同的异步任务启动和结果传递方式:
| 方式 | 作用 | 对应生产者 | 复杂度 |
|---|---|---|---|
std::async |
最常用。 自动启动一个异步任务,并返回其 future。 |
运行时自动创建线程或任务。 | 简单 |
std::packaged_task |
封装一个可调用对象,允许你手动启动它,并通过其 future 获取结果。 |
手动管理任务的启动。 | 中等 |
std::promise |
最低层。 在一个线程中设置结果或异常,通过其 future 传递给另一个线程。 |
手动在不同线程间传递结果。 | 复杂 |
1.3 std::future的关键操作
一旦获得了 std::future 对象,就可以使用它的成员函数来查询状态或获取结果:
A. 获取结果:get()
- 作用: 获取异步任务的返回值。
- 行为:
get()是阻塞式的。如果任务尚未完成,调用get()的线程会阻塞,直到任务完成并计算出结果。 - 特性:
get()只能调用一次。第二次调用会导致未定义行为。
// 假设 func 返回 int
std::future<int> f = std::async(std::launch::async, func);
// 阻塞并获取结果
int result = f.get();
B. 查询状态:wait() 和 wait_for()
- 作用: 阻塞当前线程,直到任务完成,但不获取结果。
wait(): 阻塞直到任务完成(返回void)。wait_for(duration): 阻塞指定时间。返回std::future_status枚举,用于判断当前状态:std::future_status::ready:任务已完成。std::future_status::timeout:等待超时,任务仍在进行。std::future_status::deferred:任务被延迟(如果std::async使用std::launch::deferred)。
// 等待任务完成
f.wait();
// 最多等待 100 毫秒
std::future_status status = f.wait_for(std::chrono::milliseconds(100));
if (status == std::future_status::timeout) {
std::cout << "Task is still running...\n";
}
2. 关于 future
2.1 get 和 wait 区别
| 特性 | future.get() | future.wait() |
|---|---|---|
| 返回值 | 返回异步任务计算出的实际结果(如果任务函数有返回值)。 | 返回 void。它只表示等待已完成,不提供结果。 |
| 结果获取 | 获取并消费结果(或重新抛出存储的异常)。 | 仅用于等待任务完成。 |
| 可调用次数 | 只能调用 一次。 | 可以调用多次。 |
| 对共享状态的影响 | 调用后,future 不再有效(移动语义)。 |
不改变 future 的状态,可继续使用。 |
1. std::future::get():阻塞与消费
- 阻塞: 如果异步任务尚未完成,调用
get()的线程会阻塞,直到任务完成。 - 获取结果: 任务完成后,
get()返回异步任务函数的返回值。 - 处理异常: 如果异步任务在执行过程中抛出异常,
get()会捕获该异常,并在调用线程中重新抛出。 - 消费:
get()是一个消费操作。它会转移(或说销毁)共享状态中存储的结果数据。
关键限制:只能调用一次
- 由于
get()会消费共享状态中的结果,一旦调用,该std::future对象就不能再用于获取结果。第二次调用get()将导致未定义行为(通常是程序崩溃)。
2. std::future::wait():仅阻塞
- 阻塞: 如果异步任务尚未完成,调用
wait()的线程会阻塞,直到任务完成。 - 不获取结果:
wait()不关心结果是什么,它只关心任务是否完成。它返回void。 - 不消费:
wait()不会销毁共享状态中的结果。
优势:可多次调用和配合
- 多次调用: 你可以在一个循环中或程序的不同部分多次调用
wait()来检查任务是否完成,这是安全的。 - 配合
get(): 通常的用法是先调用wait()或wait_for()来确保任务完成,然后调用get()来安全地获取结果。
📝 示例对比,假设我们有一个异步任务:
std::future<int> f = std::async([]{
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
});
使用 get()
// 主线程阻塞 1 秒,等待任务完成
int result = f.get();
std::cout << "Result: " << result << std::endl; // 输出 42
// int result2 = f.get(); // 错误!第二次调用 get() 会导致未定义行为。
使用 wait()
// 主线程阻塞 1 秒,等待任务完成
f.wait();
std::cout << "Task is done, but the result is not fetched yet." << std::endl;
// 可以在这里再次调用 f.wait();
f.wait();
// 必须调用 get() 才能获取结果,且 get() 只能调用一次。
int result = f.get();
std::cout << "Fetched result: " << result << std::endl;
2.2 wait、wait_for、wait_until 的区别
| 函数 | 阻塞方式 | 时间限制 | 返回值 | 关键词 |
|---|---|---|---|---|
wait() |
无限期阻塞 | 无限制 | void |
等到任务完成为止 |
wait_for() |
定时阻塞 | 等待一个时长 (duration) |
std::future_status |
最多等多久 |
wait_until() |
定时阻塞 | 等待直到一个绝对时间点 (time_point) |
std::future_status |
等到什么时候 |
1. wait():无限期阻塞 (Unconditional Wait)
- 参数: 无。
- 行为: 当前线程被无限期地阻塞,直到异步任务完成并将结果写入共享状态为止。
- 返回值:
void。只表示等待结束,不提供任务状态。 - 场景: 当对任务完成时间不敏感,或者必须等待任务完成后才能继续时 使用。
示例:
std::future<int> f = std::async(heavy_task);
// 主线程将在这里阻塞,直到 heavy_task 完成。
f.wait();
// 任务已完成,可以继续后续操作,或者调用 f.get() 获取结果。
std::cout << "任务完成!\n";
2. wait_for():定时阻塞 (Timed Wait)
- 参数: 一个时间间隔 (
std::chrono::duration),指定最大等待时长。 - 行为: 线程最多阻塞指定时长。如果在超时前任务完成,则等待提前结束。
- 返回值:
std::future_status枚举值,指示等待结束时的任务状态。 - 场景: 实现超时机制或非阻塞轮询。允许你在等待时限过后继续执行其他工作,而不是无限期地等待。
示例:
using namespace std::chrono;
std::future<int> f = std::async(std::launch::async, heavy_task);
// 最多等待 100 毫秒
auto status = f.wait_for(milliseconds(100));
if (status == std::future_status::ready) {
std::cout << "任务在 100ms 内完成!结果是: " << f.get() << "\n";
} else if (status == std::future_status::timeout) {
std::cout << "100ms 超时,任务仍在进行中。\n";
// 此时主线程可以去做其他事情,稍后再次检查
// 最后仍需调用 f.get() 或 f.wait() 保证任务结束。
}
3. wait_until(time_point):等待至时间点
- 参数: 一个绝对时间点 (
std::chrono::time_point),指定停止等待的时刻。 - 行为: 线程阻塞直到指定的时间点到达,或者任务提前完成。
- 返回值:
std::future_status枚举值,与wait_for相同。 - 场景: 实现基于系统时钟的精确截止时间,例如“任务必须在 2025 年 11 月 3 日上午 10:00 前完成”。
using namespace std::chrono;
// 获取当前系统时间
auto now = system_clock::now();
// 定义截止时间点:从现在起 500 毫秒后
auto deadline = now + milliseconds(500);
// 等待直到截止时间
auto status = f.wait_until(deadline);
if (status == future_status::ready) {
std::cout << "任务在截止时间前完成。\n";
} else {
std::cout << "已到达截止时间,任务未完成。\n";
}
🎯 总结
- 如果想让线程尽可能地高效运行,并在等待任务时执行其他工作,请使用
wait_for()或wait_until()实现定时等待和轮询。 - 如果只是想简单地同步代码流,且不担心任务阻塞,可以使用
wait()。 - 记住,所有这三个函数都只是等待任务完成。如果要获取任务的返回值或捕获异常,你最终都需要调用
f.get()。
3. async
std::async是 C++11 并发库中最高层、最便捷的工具,用于启动异步任务。它的设计目的是让你能像调用普通函数一样简单地启动一个可能在另一个线程中运行的函数,并自动为你管理线程的启动和结果的获取。
3.1 基本概述
std::async 简化了传统的“手动创建 std::thread + 手动创建 std::promise”的复杂流程,它将以下三步自动化了:
- 启动任务: 接收一个可调用对象(函数、Lambda 等)及其参数。
- 管理线程: 根据启动策略(
std::launch),决定是在新线程中运行,还是延迟执行。 - 返回结果代理: 返回一个
std::future对象,用于在调用线程中等待任务完成并获取结果。
基本语法
template <class F, class... Args>
std::future<typename std::result_of<F(Args...)>::type>
async(F&& f, Args&&... args);
async 的基本原理:「把任务扔给后台(线程/线程池)运行,同时立即返回一个「期货」future,让调用者可以择机 get() 结果」;
整个机制可以拆成三步:打包、调度、同步
- 打包(Bind)
std::async把 可调用对象 + 参数 拷贝一份,做成一个std::packaged_task<>对象,内部持有- 函数指针/仿函数
- 实参副本
- 与一个
std::promise<R>关联
- 调度(Schedule)
根据「启动策略」决定扔到哪:std::launch::async→ 新线程 或 线程池(实现自定)立即/稍后运行std::launch::deferred→ 惰性求值,直到future.get()才在同一线程同步调用
默认策略是async | deferred,实现可自由选择。
- 同步(Synchronize)
返回的std::future<R>内部共享状态包含:- 结果槽位(
R或exception_ptr) - 条件变量:后台任务完成时通过
promise::set_value()把结果写进去并唤醒阻塞在future::get()的线程。
- 结果槽位(
注意:线程池还是新线程?
标准不保证线程池,但主流实现(libstdc++、libc++、MSVC)都做了线程池优化:
- 短任务反复提交不会反复创建
pthread/CreateThread,而是取空闲 Worker 直接执行。 - 只有池未启用或任务溢出时才回退到「临时线程」
示例
#include <future>
#include <iostream>
long long compute_large_sum(int n) {
long long sum = 0;
for (int i = 0; i <= n; ++i) {
sum += i;
}
return sum;
}
void simple_async() {
// 1. 启动异步任务并获得 future<long long>
std::future<long long> future_result = std::async(compute_large_sum, 100000);
// 主线程可以继续做其他工作...
std::cout << "主线程:任务已启动,正在做其他事情。\n";
// 2. 阻塞并获取结果(如果任务未完成,get() 会等待)
long long result = future_result.get();
std::cout << "主线程:任务结果是: " << result << "\n";
}
3.2 启动策略
这是使用 std::async 最需要注意的地方。通过第一个可选参数,可以控制任务的启动行为。
| 策略 | 类型 | 行为 | 解释 |
|---|---|---|---|
std::launch::async |
异步执行 | 保证在新线程中立即(或尽快)执行任务。 | 任务在新线程中并行运行。 |
std::launch::deferred |
延迟执行 | 不创建新线程。 任务被延迟到调用 future.get() 或 future.wait() 时,在调用线程中同步执行。 |
任务是惰性的,只在需要结果时才执行。 |
| 默认策略 | 由实现决定 | `std::launch::async | std::launch::deferred`。由运行时库自行选择是立即异步执行还是延迟同步执行。 |
策略示例
// 保证在新线程中运行
std::future<int> f1 = std::async(std::launch::async, my_function);
// 任务只在调用 f2.get() 时,在主线程同步执行
std::future<int> f2 = std::async(std::launch::deferred, my_function);
// 行为不确定,由编译器/运行时决定
std::future<int> f3 = std::async(my_function);
3.3 async 陷阱:future 的生命周期
这是 std::async 最容易出错的地方,尤其是在使用默认策略时。
C++ 标准规定: 当 std::future 对象被销毁时(即调用其析构函数),如果它是通过 std::async 创建的,并且共享状态仍然有效,那么析构函数会阻塞,直到异步任务完成。这类似于隐式地调用了 wait()。
🚨 示例:当 future 是一个临时对象时,问题尤为突出:
void run_and_wait() {
// WARNING: 这是一个常见的错误!
// 任务启动后,future 立即作为临时对象被销毁。
// future 的析构函数会阻塞 run_and_wait() 线程,直到 compute_large_sum 完成。
// 这导致了任务**同步执行**(即使它可能被启动在一个新线程)。
std::async(compute_large_sum, 50000); // 临时 future 在此行末尾被销毁并等待
std::cout << "如果任务耗时,这行代码会被阻塞。\n";
}
void correct_way() {
// 正确的做法:将 future 存储在一个具名变量中,延长其生命周期
std::future<long long> f = std::async(compute_large_sum, 50000);
std::cout << "这行代码可以立即执行。\n";
// 函数退出时,f 析构,此时会阻塞等待任务结束。
}
记住: 只要你使用 std::async,就必须将返回的 std::future 对象存储在一个变量中,以控制任务的等待时机。
3.4 小结
std::async 是启动异步任务的“一站式服务”,让你能够:
- 启动任务: 简单地调用函数,无需手动管理线程。
- 控制行为: 通过
std::launch策略控制是并行还是延迟执行。 - 获取结果: 通过返回的
std::future对象安全地获取结果或异常。
和 std::thread 对比
| std::thread | std::async | |
|---|---|---|
| 返回值 | 无(需自己搞) | 自动返回 future<R> |
| 异常传递 | 线程里抛 = std::terminate() |
抛异常 → 保存在 future 里,get() 再抛 |
| 调度策略 | 总是新线程 | 可异步也可惰性 |
| 资源管理 | 需手动 join/detach |
析构会阻塞等待(*) |
最佳实践:
- 明确策略: 尽量使用
std::launch::async来保证并行性,或使用std::launch::deferred来保证惰性。避免使用默认策略。 - 存储
future: 始终将std::async的结果赋值给一个具名std::future变量。
4. promise
4.1 基本概述
std::promise 的核心在于手动控制共享状态的写入。
| 角色 | 作用 | 对应对象 |
|---|---|---|
| 生产者 (Writer) | 负责执行任务并将结果或异常写入共享状态。 | std::promise |
| 消费者 (Reader) | 负责从共享状态中读取结果或异常。 | std::future |
关系链:
- 生产者创建
std::promise<T>。 - 生产者调用
promise.get_future()获得关联的std::future<T>。 - 生产者将
future传递给消费者线程(例如通过函数参数或返回)。 - 生产者在新线程中执行计算,并通过
promise.set_value()或promise.set_exception()写入结果。 - 消费者调用
future.get()阻塞等待并获取结果。
4.2 成员函数
std::promise 主要提供了用于写入共享状态的三个函数:
A. 获取 future
| 函数 | 作用 | 描述 |
|---|---|---|
get_future() |
建立连接 | 从 std::promise 对象获取一个关联的 std::future 对象。一个 promise 只能调用一次该函数。 |
B. 设置值(成功)
| 函数 | 作用 | 描述 |
|---|---|---|
set_value(T value) |
写入结果 | 成功完成任务后,将计算出的值 value 写入共享状态,并使关联的 future 状态变为 ready。 |
一句话区别
set_value():立即把结果写进共享状态并唤醒等待线程。set_value_at_thread_exit():**延迟到当前线程完全退出(所有线程局部对象已析构)**后才写值并唤醒。
C. 设置异常(失败)
| 函数 | 作用 | 描述 |
|---|---|---|
set_exception(std::exception_ptr p) |
写入异常 | 当任务因异常终止时,将异常指针 p 写入共享状态,使关联的 future 状态变为 ready。当 future.get() 被调用时,该异常会被重新抛出。 |
重要的自动化行为:析构
与 std::future 的析构行为类似,std::promise 也有一个重要的析构行为:
- 如果
std::promise在未设置值或异常的情况下被销毁,并且其关联的future仍然有效,那么std::promise的析构函数会自动调用set_exception(),并在共享状态中写入一个std::future_error异常。
这保证了消费者线程在调用 future.get() 时,不会无限期阻塞,而是会收到一个异常通知,告知任务失败(生产者没有提供结果)。=
示例:线程间的结果传递
// 生产者函数:运行在新线程中
void producer_task(std::promise<int>& prom) {
try {
std::this_thread::sleep_for(std::chrono::seconds(1));
int result = 42;
// 1. 将结果设置到 promise 中
prom.set_value(result);
} catch (...) {
// 2. 如果发生异常,捕获并设置到 promise 中
prom.set_exception(std::current_exception());
}
}
int main() {
// 1. 创建 promise 对象
std::promise<int> promise_obj;
// 2. 从 promise 获取 future 对象
std::future<int> future_obj = promise_obj.get_future(); // future 将被传递给主线程使用
// 3. 启动生产者线程,将 promise 对象以引用形式传递
std::thread t(producer_task, std::ref(promise_obj)); // 注意:promise 对象是不可复制的
std::cout << "主线程:已启动计算,正在等待结果...\n";
try {
// 4. 主线程通过 future 阻塞并获取结果
int value = future_obj.get();
std::cout << "主线程:成功收到结果: " << value << "\n";
} catch (const std::exception& e) {
std::cout << "主线程:捕获到异常: " << e.what() << "\n";
}
t.join();
return 0;
}
4.3 应用场景
虽然 std::async 已经能处理大多数异步场景,但 std::promise 在以下情况下不可替代:
- 低层结果传递: 当你需要对结果设置过程进行精确控制,或者在复杂的线程结构中手动传递结果时。
- 信号量: 可以用
promise来实现简单的线程同步机制,让一个线程通过设置值或异常来“发出信号”,解除另一个线程的阻塞。 - 连接第三方 API: 当你使用一些不返回
std::future的底层库或 API 时,你可以用std::promise封装这些调用,然后手动设置结果,从而将底层调用适配到 C++ 的future体系。
std::promise 是理解 C++ 异步编程底层机制的关键,它揭示了 std::async 和 std::packaged_task 内部是如何工作的。
5. shared_future
5.1 基本概述
理解 std::shared_future,首先要回顾 std::future 的限制:
| 特性 | std::future | std::shared_future |
|---|---|---|
| 可复制性 | 不可复制(只能被移动)。 | 可复制(可被共享)。 |
get() 调用 |
只能调用一次(消费共享状态)。 | 可以被多次调用(只读访问共享状态)。 |
| 所有权 | 对共享状态拥有独占所有权。 | 对共享状态拥有共享所有权。 |
| 用途 | 单个线程获取结果。 | 多个线程等待并获取同一个结果。 |
简而言之: std::future 是独占的,一旦使用就失效;而 std::shared_future 是共享的,可以被复制给多个线程使用。
5.2 如何创建 std::shared_future?
std::shared_future 不能直接从 std::async 或 std::promise 获得,它必须通过现有的 std::future 对象转换而来。
A. 移动转换 (share() 成员函数)
这是最常见的转换方式。它将 std::future 对象移动为一个 std::shared_future 对象。转换后,原来的 std::future 对象将失效 (valid() == false)。
std::future<int> fut = std::async(some_task);
// 将独占的 fut 移动转换为共享的 sf
std::shared_future<int> sf = fut.share();
// 此时 fut.valid() == false;
B. 复制构造
一旦拥有了一个 std::shared_future 对象,你就可以像复制普通对象一样复制它。
std::shared_future<int> sf1 = original_future.share();
// 复制 sf1,sf2 和 sf1 共同指向同一个共享状态
std::shared_future<int> sf2 = sf1;
应用:多线程广播结果
std::shared_future 的典型应用是实现“广播”机制,即一个任务完成后,同时通知并为多个等待者提供结果。
int heavy_calculation() {
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Task finished calculation.\n";
return 100;
}
// 消费者线程函数
void consumer_task(std::shared_future<int> sf, int id) {
std::cout << "Thread " << id << " is waiting.\n";
// sf.wait() 和 sf.get() 都可以被安全调用多次
sf.wait();
// 所有线程都会阻塞直到任务完成,然后都获取到相同的结果
int result = sf.get();
std::cout << "Thread " << id << " received result: " << result << "\n";
}
void shared_future_example() {
// 1. 启动任务并获得独占 future
std::future<int> fut = std::async(std::launch::async, heavy_calculation);
// 2. 转换为 shared_future
std::shared_future<int> sf = fut.share();
// 3. 启动多个线程,并将 sf 复制给它们
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i) {
// 注意:sf 是可复制的,这里传递的是 sf 的副本
threads.emplace_back(consumer_task, sf, i + 1);
}
// 4. 等待所有线程完成
for (auto& t : threads) {
t.join();
}
}
运行结果(逻辑):
- 所有线程 1, 2, 3 都在
sf.wait()/sf.get()处阻塞。 heavy_calculation在后台运行 2 秒后完成。- 所有阻塞的线程几乎同时被唤醒,并且都安全地获取到结果
100。
5.3 get() 的行为差异
在使用 std::shared_future 时,你需要知道 get() 的行为:
- 阻塞: 如果共享状态尚未就绪,
sf.get()依然会阻塞当前线程。 - 不消费: 与
std::future::get()不同,std::shared_future::get()不会使共享状态失效。它只是从共享状态中读取一个副本,因此可以被同一个或不同的std::shared_future副本多次调用。
6. package_task
6.1 基本概述
std::packaged_task 是一种可调用对象包装器,它自动将函数的执行与 std::future 结果的设置关联起来。
| 角色 | 作用 |
|---|---|
| 封装 | 封装任何可调用对象(函数、Lambda、函数对象)。 |
| 生产者 | 当 packaged_task 被调用时,它执行被封装的函数,并自动将函数的返回值或抛出的异常写入其内部的共享状态。 |
| 结果代理 | 通过 get_future() 方法,提供一个 std::future 对象,让消费者线程可以获取结果。 |
与其他组件的对比
| 组件 | 主要功能 | 结果如何设置? |
|---|---|---|
std::async |
自动启动任务并返回 future。 |
系统自动设置结果。 |
std::promise |
手动设置结果。 | 开发者手动调用 set_value() 或 set_exception()。 |
std::packaged_task |
封装任务,并返回 future。 |
调用 task() 时自动设置结果。 |
6.2 用法
std::packaged_task 通常用于以下场景:将任务与结果代理打包在一起,然后将任务交给一个自定义的线程管理系统(如 std::thread 或线程池)。
步骤一:创建和封装
std::packaged_task 是一个模板类,你需要指定被封装函数的签名(返回值和参数)。
#include <future>
#include <iostream>
// 假设有一个函数签名: int(int, int)
int add(int a, int b) {
std::cout << "Task running in thread: " << std::this_thread::get_id() << std::endl;
if (a < 0 || b < 0) {
throw std::runtime_error("Negative input not allowed.");
}
return a + b;
}
void section_packaged_task() {
std::cout << "--- Section: std::packaged_task ---\n";
// 1. 封装任务:指定函数签名 int(int, int)
std::packaged_task<int(int, int)> task(add);
// 2. 获取 future
std::future<int> fut = task.get_future();
// 3. 将任务移动到新线程中执行
// 注意:task 是不可复制的,只能移动
std::thread t(std::move(task), 10, 20); // 线程会执行 task(10, 20)
std::cout << "主线程:等待结果...\n";
// 4. 主线程通过 future 阻塞并获取结果
int result = fut.get();
std::cout << "主线程:计算结果: " << result << "\n";
t.join();
}
步骤二:作为可调用对象执行
std::packaged_task 重载了 () 运算符,使其可以像普通函数一样被调用。
// 假设这是主线程
std::packaged_task<int()> manual_task([]{ return 99; });
// 在主线程中同步执行任务
manual_task(); // 任务执行,结果 99 被设置到共享状态
6.3 异常处理
std::packaged_task 的一个强大之处在于其自动的异常处理。
如果在 task() 调用中,被封装的函数抛出了异常,packaged_task 会自动捕获该异常,并使用 set_exception() 将其存储在共享状态中。当 future.get() 被调用时,异常会在消费者线程中被重新抛出。
// 假设我们在上面的示例中执行:
// std::thread t(std::move(task), -5, 10);
try {
int result = fut.get(); // 这里会重新抛出 runtime_error
} catch (const std::runtime_error& e) {
std::cout << "捕获到异常: " << e.what() << std::endl; // 输出 "Negative input not allowed."
}
6.4 应用场景
std::packaged_task 最有价值的场景是线程池 (Thread Pool) 的实现。
- 任务队列: 线程池的主线程将
std::packaged_task对象包装起来,放入一个线程安全的任务队列。 - 工作线程: 工作线程从队列中取出
packaged_task对象。 - 执行: 工作线程直接调用
task()运算符执行任务。 - 结果返回: 任务执行后,结果自动通过
future传递给请求任务的原始调用方。
通过这种方式,packaged_task 完美地将异步任务、结果获取和线程管理解耦开来。
【★,°:.☆( ̄▽ ̄)/$:.°★ 】那么本篇到此就结束啦,如果有不懂 和 发现问题的小伙伴可以在评论区说出来哦,同时我还会继续更新关于【C++】的内容,请持续关注我 !!

更多推荐



所有评论(0)