在传统的 C++ 多线程编程中,我们经常遇到一个棘手的问题:如何安全地获取线程执行的结果?假设你创建了一个线程来计算斐波那契数列的第 30 项,你当然希望最终能拿到这个计算结果。但问题来了,线程是异步执行的,主线程如何知道子线程什么时候计算完成?又该如何拿到这个结果?
我曾经在一个项目中就踩过这样的坑。当时我天真地使用了一个全局变量来存储线程的计算结果,结果程序运行时偶尔会崩溃。后来发现是因为多个线程同时读写这个全局变量导致了数据竞争。这种通过共享变量来传递结果的方式,不仅代码难以维护,而且存在严重的安全隐患。
std::future 的出现就是为了解决这个问题。它就像你和线程之间的一个约定:线程承诺在未来某个时刻会给你一个结果,而你可以通过 future 对象来查询或等待这个结果。这种机制完全避免了显式的共享数据,从根本上解决了数据竞争的问题。
提示:std::future 是 C++11 引入的重要特性,它提供了一种线程安全的方式来获取异步操作的结果。
std::future 通常不会直接创建,而是通过以下三种方式获得:
让我们看一个最简单的例子,使用 std::async 创建异步任务:
cpp复制#include <iostream>
#include <future>
int calculate() {
// 模拟耗时计算
std::this_thread::sleep_for(std::chrono::seconds(1));
return 42;
}
int main() {
// 启动异步任务
std::future<int> fut = std::async(std::launch::async, calculate);
// 在主线程做其他事情...
std::cout << "正在等待结果..." << std::endl;
// 获取结果(会阻塞直到结果就绪)
int result = fut.get();
std::cout << "计算结果: " << result << std::endl;
return 0;
}
这个例子展示了 std::future 最典型的用法。std::async 启动了一个异步任务,并返回一个 future 对象。当我们需要结果时,调用 get() 方法即可。
每个 future 对象都有一个关联的共享状态,这个状态可以是:
我们可以通过 valid() 方法检查 future 是否有效:
cpp复制std::future<int> fut; // 默认构造的 future 是无效的
if (!fut.valid()) {
std::cout << "future 无效" << std::endl;
}
fut = std::async(std::launch::async, []{ return 1; });
if (fut.valid()) {
std::cout << "future 现在有效了" << std::endl;
}
std::async 是最简单直接的创建 future 的方式。它有两种启动策略:
cpp复制// 立即异步执行
auto fut1 = std::async(std::launch::async, []{
std::this_thread::sleep_for(std::chrono::seconds(1));
return "立即执行";
});
// 延迟执行
auto fut2 = std::async(std::launch::deferred, []{
std::this_thread::sleep_for(std::chrono::seconds(1));
return "延迟执行";
});
std::cout << fut1.get() << std::endl; // 这里可能已经执行完成
std::cout << fut2.get() << std::endl; // 这里才开始执行
std::packaged_task 是一个可调用对象的包装器,它可以将任何可调用对象与 future 关联起来:
cpp复制#include <future>
#include <iostream>
#include <thread>
int main() {
// 创建一个 packaged_task,包装一个 lambda 表达式
std::packaged_task<int(int,int)> task([](int a, int b) {
return a + b;
});
// 获取关联的 future
std::future<int> fut = task.get_future();
// 在另一个线程上执行任务
std::thread(std::move(task), 2, 3).detach();
// 获取结果
std::cout << "2 + 3 = " << fut.get() << std::endl;
return 0;
}
std::promise 提供了最灵活的方式来设置 future 的值,它允许你在任何地方设置结果:
cpp复制#include <future>
#include <iostream>
#include <thread>
void worker(std::promise<int> prom) {
// 模拟耗时操作
std::this_thread::sleep_for(std::chrono::seconds(1));
// 设置结果
prom.set_value(42);
}
int main() {
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread(worker, std::move(prom)).detach();
// 等待结果
std::cout << "结果是: " << fut.get() << std::endl;
return 0;
}
除了 get() 方法会阻塞直到结果就绪外,std::future 还提供了其他等待方式:
cpp复制std::future<int> fut = std::async(std::launch::async, []{
std::this_thread::sleep_for(std::chrono::seconds(2));
return 100;
});
// 等待结果就绪
fut.wait();
// 带超时的等待
if (fut.wait_for(std::chrono::seconds(1)) == std::future_status::ready) {
std::cout << "结果已就绪" << std::endl;
} else {
std::cout << "超时,结果还未就绪" << std::endl;
}
如果异步任务中抛出了异常,这个异常会通过 future 传播到调用 get() 的线程:
cpp复制std::future<void> fut = std::async(std::launch::async, []{
throw std::runtime_error("任务中发生了错误");
});
try {
fut.get();
} catch (const std::exception& e) {
std::cerr << "捕获到异常: " << e.what() << std::endl;
}
std::future 只能被获取一次结果,如果需要多个地方获取结果,可以使用 std::shared_future:
cpp复制std::promise<int> prom;
std::shared_future<int> shared_fut = prom.get_future().share();
// 多个线程可以共享这个 future
std::thread t1([shared_fut]{
std::cout << "线程1得到: " << shared_fut.get() << std::endl;
});
std::thread t2([shared_fut]{
std::cout << "线程2得到: " << shared_fut.get() << std::endl;
});
prom.set_value(100);
t1.join();
t2.join();
在使用 std::future 时,有几个常见的坑需要注意:
在实际项目中,频繁创建线程的开销可能很大。我曾在性能测试中发现,当任务很轻量时,使用 std::async 创建大量线程反而比单线程执行更慢。这时可以考虑使用线程池模式,配合 std::packaged_task 和 std::future 来管理任务。
std::future 可以很好地与 C++ 其他多线程工具配合使用。例如,结合 std::condition_variable 可以实现更复杂的同步模式:
cpp复制std::promise<void> ready_promise;
std::shared_future<void> ready_future = ready_promise.get_future().share();
std::mutex mtx;
std::condition_variable cv;
bool data_ready = false;
// 工作线程
std::thread worker([&]{
// 等待启动信号
ready_future.wait();
std::unique_lock<std::mutex> lock(mtx);
// 处理数据...
data_ready = true;
cv.notify_all();
});
// 主线程
std::cout << "准备启动工作线程..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
ready_promise.set_value(); // 启动所有工作线程
{
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []{ return data_ready; });
std::cout << "工作完成" << std::endl;
}
worker.join();
在没有 std::future 之前,我们通常使用以下几种方式获取线程结果:
std::future 提供了一种更优雅的解决方案。它不仅线程安全,而且代码更清晰直观。下面是一个对比示例:
传统方式(使用全局变量):
cpp复制int result; // 全局变量
std::mutex mtx;
void worker() {
std::lock_guard<std::mutex> lock(mtx);
result = 42; // 设置结果
}
int main() {
std::thread t(worker);
t.join();
std::lock_guard<std::mutex> lock(mtx);
std::cout << result << std::endl;
return 0;
}
现代方式(使用 std::future):
cpp复制int worker() {
return 42;
}
int main() {
auto fut = std::async(std::launch::async, worker);
std::cout << fut.get() << std::endl;
return 0;
}
明显可以看出,使用 std::future 的代码更简洁、更安全,也更易于维护。