1. 异步编程
std::future 和 std::async 是 C++11 异步编程的核心组件,定义在<future>头文件中:
- std::future 是异步操作结果 的 "句柄",用于在未来获取异步任务的返回值或异常;
- std::async 是异步任务启动器,简化了异步任务的创建,无需手动管理线程和结果传递,直接返回 std::future。
两者结合可高效实现 "启动任务→主线程继续工作→按需获取结果" 的异步编程模式,替代了传统手动创建线程 + 全局变量 / 回调函数的繁琐方式。
2. std::async
std::async 是函数 模板,用于异步启动一个可调用对象 (函数、lambda、仿函数等),并返回关联的 std::future,简化了 "线程创建 + 结果传递" 的流程。
cpp
// 显式指定启动策略
template <class Fn, class... Args>
std::future<typename std::result_of<Fn(Args...)>::type>
async(std::launch policy, Fn&& fn, Args&&... args);
// 默认策略(由实现决定)
template <class Fn, class... Args>
std::future<typename std::result_of<Fn(Args...)>::type>
async(Fn&& fn, Args&&... args);
std::async 的参数与 std::thread 构造函数的参数十分相似,但是多了一个代表异步策略的参数。
异步启动可调用对象有两种策略,由 policy 参数决定:
|---------------------------|-----------------------------------------------------------------------------------------------|
| 策略 | 行为 |
| std::launch::async | 强制异步:立即创建新线程执行任务,任务在独立线程中运行。 |
| std::launch::deferred | 延迟执行:任务不会立即启动,直到调用 future::get()/future::wait() 时,才在当前线程中执行(无新线程);若永远不调用 get()/wait(),任务永不执行。 |
| 默认策略(不传递参数) | 实现可选择两种策略之一(通常根据系统资源动态决定,如线程池负载)。 |
例如:
cpp
#include <thread>
#include <iostream>
#include <future>
using namespace std;
void func(const string& name)
{
cout << name << "正在执行..." << endl;
}
int main()
{
// 启动一个线程异步执行func
auto f1 = async(launch::async, func, "异步任务");
// 延迟执行func
auto f2 = async(launch::deferred, func, "延迟任务");
cout << "主线程任务执行中..." << endl;
this_thread::sleep_for(chrono::seconds(3));
cout << "主线程任务执行完毕,开始获取异步任务与延迟任务的返回值" << endl;
// 阻塞等待异步任务执行完成,获得结果,相当于pthread中的join
f1.get();
// 在主线程中立即执行延迟任务,获得结果
f2.get();
return 0;
}
结果如下:
3. std::future
std::future<T> 是模板类 (T 为异步任务的返回类型,若任务无返回值则用 std::future<void>),表示一个尚未完成的异步操作的结果。
主要成员函数:
|-------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
| 函数 | 功能 |
| T get() | (1)获取异步任务结果(阻塞直到完成); (2)任务抛异常时,get() 会重新抛出该异常; (3)仅可调用一次(结果以移动的方式返回)。 |
| bool valid() const | 检查 future 是否关联到有效的异步操作(默认构造 /move 后 /get() 后均为 false)。建议在调用get之前先调用该函数检验。 |
| void wait() | 阻塞当前线程,直到异步任务完成(仅等待,不获取结果)。 |
| future_status wait_for(const chrono::duration& rel_time) | 阻塞指定时长,返回状态: - future_status::ready :任务完成; -future_status::timeout :超时,任务未完成; - future_status::deferred:任务被延迟执行(未启动)。 |
| future_status wait_until(const chrono::time_point& abs_time) | 阻塞直到指定时间点,返回值同 wait_for。 |
| void swap(future& other) | 交换两个 future 的状态。 |
| future(future&& other) | 移动构造(future 不可拷贝,仅可移动)。 |
注意事项:
-
一个future对象的 get() 只能调用一次,调用后 future 变为无效状态(valid() 返回 false);
-
若异步任务未完成,调用 get()/wait() 会阻塞当前线程,直到结果可用;
-
异步任务抛出的异常会被捕获并存储 ,调用 get() 时重新抛出。例如:
cppint risky_task() { throw std::runtime_error("异步任务出错!"); return 42; } int main() { std::future<int> fut = std::async(risky_task); try { int res = fut.get(); // 重新抛出任务中的异常 std::cout << "结果:" << res << std::endl; } catch (const std::runtime_error& e) { std::cout << "捕获异常:" << e.what() << std::endl; // 输出:异步任务出错! } return 0; } -
future对象的析构函数会阻塞等待异步任务完成 。若临时的future对象没有被变量接收的话,就会导致主线程在该行阻塞等待异步任务完成。
cppvoid long_task() { std::this_thread::sleep_for(std::chrono::seconds(5)); std::cout << "异步任务完成" << std::endl; } int main() { std::async(std::launch::async, long_task); // 阻塞等待5秒 // 其他任务 // ... return 0; }
若需多次获取异步结果 (如多线程共享结果),可使用 std::shared_future:
-
可拷贝,get() 可调用多次;
-
可通过 std::future::share() 或移动构造得到:
cppstd::future<int> fut = std::async([](){ return 42; }); std::shared_future<int> sfut = fut.share(); // fut 变为无效 // 多线程可同时调用 sfut.get()
4. std::future 的其他数据源
std::future 的结果不仅可来自 std::async,还可通过 std::promise 或 std::packaged_task 手动关联,std::async 本质是对这两者的封装:
4.1 std::promise
std::promise 封装了 std::future。我们可以通过get_future 获取 promise 对象内部的 future 对象 ,同时也可以使用其他成员函数对 future 对象写入值或异常。
这些写入行为都会导致 future 对象就绪。
|----------------------------------|--------------------------------------|
| 函数 | 功能 |
| get_future | 返回与 promise 对象关联的 future 对象(仅可调用一次)。 |
| set_value | 向 future 对象写入值。 |
| set_exception | 向 future 对象写入异常。 |
| set_value_at_thread_exit | 在线程退出时向 future 对象写入值。 |
| set_exception_at_thread_exit | 在线程退出时向 future 对象写入异常。 |
例如:
cpp
std::promise<int> prom;
std::future<int> fut = prom.get_future();
// 新线程中设置值
std::thread t([&prom]() {
std::this_thread::sleep_for(std::chrono::seconds(2));
prom.set_value(100); // 设置结果,future 可获取
});
std::cout << "promise 结果:" << fut.get() << std::endl; // 输出 100
t.join();
4.2 std::packaged_task
std::packaged_task 是 C++11 并发库中封装可调用对象并关联异步结果的模板类,核心作用是将 "可调用对象(函数、lambda、仿函数等)" 与 std::future 绑定。
相比于std::function,std::packaged_task对象在被调用后不会直接返回可调用对象的结果(或抛异常),而是将结果(或异常)保存到与其关联的future对象当中。
与promise相同,可通过get_future接口获取与其关联的future对象。
主要成员函数:
|--------------------------------------------------|---------------------------------------------------------------------------------------------------------|
| 函数 | 功能 |
| std::future<Ret> get_future() | 获取与当前 packaged_task 关联的 future; 仅可调用一次,二次调用抛出 std::future_error。 |
| void operator()(Args... args) | 执行包装的可调用对象,传入参数 args; 执行结果会自动设置到关联的 future(正常结果 / 异常); 仅可调用一次(除非调用 reset() 重置),重复调用抛 std::future_error。 |
| void make_ready_at_thread_exit(Args... args) | 延迟执行并设置结果: 1. 立即执行包装的任务; 2. 线程退出时才将结果写入 future; 避免 "结果写入后,局部变量析构" 导致的悬空引用。 |
| void reset() | 重置 packaged_task 状态: 1. 放弃当前未完成的结果; 2. 重新绑定可调用对象(保留原对象); 重置后可再次调用 operator() 和 get_future()。 |
| bool valid() const | 检查 packaged_task 是否关联有效的可调用对象(默认构造 /move 后 /reset 前可能为 false)。 |
我们可以使用packaged_task来实现线程池,下面是核心原理的极简示例:
cpp
#include <thread>
#include <iostream>
#include <future>
#include <queue>
using namespace std;
template <typename T>
void taskRunner(queue<packaged_task<T>>& taskQueue, mutex& queueMtx, condition_variable& cv)
{
while (true)
{
// 获取一个任务
packaged_task<T> task;
{
unique_lock<mutex> lock(queueMtx);
while (taskQueue.empty())
{
cv.wait(lock);
}
task = move(taskQueue.front());
taskQueue.pop();
}
// 空任务表示退出
if (!task.valid())
break;
// 执行任务
task();
}
}
int main()
{
queue<packaged_task<int()>> taskQueue;
queue<future<int>> resQueue;
mutex queueMtx;
condition_variable cv;
// 启动任务执行线程
thread worker(taskRunner<int()>, ref(taskQueue), ref(queueMtx), ref(cv));
// 生成并插入任务
for (int i = 0; i < 5; i++)
{
packaged_task<int()> task([i]() { return i * i; });
// 保存结果
resQueue.emplace(task.get_future());
// 插入任务
{
lock_guard<mutex> lock(queueMtx);
taskQueue.emplace(move(task));
}
// 唤醒任务执行线程
cv.notify_one();
}
// 插入空任务表示结束
{
lock_guard<mutex> lock(queueMtx);
taskQueue.emplace();
cv.notify_one();
}
worker.join();
// 打印结果
while (!resQueue.empty())
{
cout << "执行结果: " << resQueue.front().get() << endl;
resQueue.pop();
}
return 0;
}