掌控异步任务:解析 C++ 中 future、promise、packaged_task 与 async
1. std::future
std::future 是获取异步任务结果的核心工具。它提供了一种机制,使我们能够等待异步操作完成并获取其结果。
1.1 工作原理
std::future 和 std::promise 配合工作:
- std::promise 设置任务的结果。
- std::future 是获取这些结果的接口。 当结果尚未准备好时,调用 get() 会阻塞当前线程,直到结果可用。
1.2 API 详解
主要函数
get()
- 获取异步结果。如果结果尚未准备好,会阻塞直到结果可用。
- 一旦调用 get(),结果被取走,不能再次调用。
- 如果任务抛出异常,get() 会重新抛出该异常。
wait()
- 等待结果就绪,但不获取结果。
wait_for() 和 wait_until()
- 使用超时机制等待结果。
- 返回值:
- std::future_status::ready 表示结果已就绪。
- std::future_status::timeout 表示超时。
- std::future_status::deferred 表示任务被延迟(通常与 std::launch::deferred 结合使用)。
注意事项
- std::future 不可复制,只能移动。
- 当任务发生异常,std::future 会通过 get() 抛出对应异常。
示例
#include <iostream>
#include <future>
#include <thread>
void producer(std::promise<int> &prom) {
try {
std::this_thread::sleep_for(std::chrono::seconds(2));
prom.set_value(10);
} catch (...) {
prom.set_exception(std::current_exception());
}
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread t(producer, std::ref(prom));
std::cout << "Waiting for result..." << std::endl;
std::cout << "Result: " << fut.get() << std::endl; // 阻塞直到结果就绪
t.join();
return 0;
}
2. std::promise
std::promise 是一个生产者工具,用于将任务的结果设置给 std::future。
2.1 原理
std::promise 的作用是创建一个共享状态,并允许生产者通过 set_value 或 set_exception 向共享状态中写入数据。std::future 是消费这一数据的接口。
2.2 使用方法
主要函数
set_value(T value)
- 将结果写入到共享状态。
set_exception(std::exception_ptr e)
- 设置异常,当消费者调用 get() 时,抛出该异常。
get_future()
- 获取与 std::promise 关联的 std::future。
注意事项
- std::promise 只能设置一次结果,否则会抛出异常 std::future_error。
- 如果 std::promise 对象销毁时未设置结果,std::future 的 get() 会抛出异常。
示例
#include <iostream>
#include <future>
#include <thread>
void task(std::promise<std::string> prom) {
try {
std::this_thread::sleep_for(std::chrono::seconds(2));
prom.set_value("Hello from promise!");
} catch (...) {
prom.set_exception(std::current_exception());
}
}
int main() {
std::promise<std::string> prom;
std::future<std::string> fut = prom.get_future();
std::thread t(task, std::move(prom));
std::cout << "Waiting for result..." << std::endl;
try {
std::cout << fut.get() << std::endl;
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
}
t.join();
return 0;
}
3. std::packaged_task
std::packaged_task 是将一个可调用对象(函数、lambda 等)与异步结果(std::future)绑定的工具。
3.1 原理
- 将一个可调用对象封装到 std::packaged_task 中。
- 调用封装的任务时,它会自动将结果写入共享状态,消费者通过关联的 std::future 获取结果。
3.2 使用方法
主要函数
构造函数
- 使用可调用对象(函数、函数对象或 lambda)初始化任务。
operator()
- 执行任务,计算结果。
get_future()
- 获取关联的 std::future。
注意事项
- 和 std::promise 类似,std::packaged_task 只能设置一次结果。
- 任务执行后,结果存储在 std::future 中。
示例
#include <iostream>
#include <future>
#include <thread>
int compute(int x, int y) {
return x + y;
}
int main() {
std::packaged_task<int(int, int)> task(compute); // 包装任务
std::future<int> fut = task.get_future(); // 获取 future
std::thread t(std::move(task), 10, 20); // 在线程中执行任务
t.join();
std::cout << "Result: " << fut.get() << std::endl; // 获取结果
return 0;
}
4. std::async
std::async 是高层次的工具,用于快速启动异步任务。
4.1 原理
std::async 启动任务并自动管理其生命周期。它返回一个 std::future,用于获取任务的结果。
启动策略
std::launch::async
- 在新线程中异步启动任务。
std::launch::deferred
- 任务被延迟执行,只有在调用 get() 或 wait() 时才会运行。
默认策略
- 具体行为由实现决定,可能选择异步启动或延迟执行。
4.2 使用方法
示例
#include <iostream>
#include <future>
#include <thread>
int compute(int x) {
std::this_thread::sleep_for(std::chrono::seconds(2));
return x * x;
}
int main() {
std::future<int> fut = std::async(std::launch::async, compute, 10);
std::cout << "Doing other work..." << std::endl;
std::cout << "Result: " << fut.get() << std::endl; // 获取结果
return 0;
}
5. 四者的关系与对比
5.1 功能比较
工具 | 功能 | 特点 |
std::future | 提供异步结果的读取接口 | 只负责读取,需搭配其他工具使用。 |
std::promise | 手动设置异步结果或异常 | 提供生产者角色,控制更灵活。 |
std::packaged_task | 封装任务并生成 std::future | 自动调用任务并存储结果。 |
std::async | 启动异步任务并返回 std::future | 高层次工具,简化线程管理。 |
5.2 适用场景
std::future:
- 需要获取任务结果,但无需创建或控制任务。
- 适用于轻量级结果获取。
std::promise:
- 需要手动设置任务结果或传递异常。
- 适用于生产者-消费者模型。
std::packaged_task:
- 需要封装已有任务并异步执行。
- 适用于将现有逻辑整合为异步任务。
std::async:
- 简单启动并行任务,不需要额外线程管理。
- 适用于轻量级并行计算。
6. 常见问题与优化
6.1 超时机制
wait_for 和 wait_until 提供超时等待功能,避免长期阻塞:
if (fut.wait_for(std::chrono::seconds(1)) == std::future_status::timeout) {
std::cout << "Task timeout!" << std::endl;
}
6.2 异常处理
异步任务可能抛出异常,`std::future` 会传播异常到消费者:
try {
fut.get();
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
}
6.3 线程安全性
- std::future 和 std::promise 本身是线程安全的。
- 需要确保其他共享数据的访问同步。
7.C++ 异步编程工具总结
C++ 提供了一套强大的异步编程工具,帮助开发者简化多线程任务的结果管理和线程间通信。这些工具包括:
- std::future:
用于异步获取任务结果的占位符,是消费异步结果的核心工具。通过 get() 获取结果,阻塞线程直到结果可用。 - std::promise:
一个生产者工具,用于向共享状态手动设置任务结果或异常。它与 std::future 配合使用,实现任务结果的写入和读取。 - std::packaged_task:
封装一个可调用对象(如函数或 lambda),将其结果与 std::future 绑定,允许任务独立运行并异步返回结果。 - std::async:
高层次的接口,用于快速启动异步任务。它自动管理任务生命周期,并返回与任务结果关联的 std::future。
设计意义
- 提升并发性能:在多核系统上充分利用资源,实现任务并行化。
- 简化线程管理:通过高层接口(如 std::async),减少手动管理线程的复杂性。
- 安全通信机制:通过 std::future 和 std::promise 提供线程安全的任务结果传递。
适用场景
- 轻量级任务并行化:std::async 和 std::future。
- 手动控制任务结果:std::promise 和 std::packaged_task。
- 封装复杂计算逻辑:std::packaged_task。
C++ 异步编程工具灵活且强大,适用于从简单任务到复杂并发场景,帮助开发者更高效地编写可靠的多线程程序。