深入理解C++并发编程中的future机制
前言
在现代C++并发编程中,future是一种非常重要的同步机制,它允许我们异步获取任务执行结果。本文将深入探讨C++标准库中的future机制,帮助读者全面理解其工作原理和使用方法。
future的基本概念
想象这样一个场景:你去交通枢纽乘飞机,办理完手续后需要在候机区等待通知。在这段等待时间里,你可以做其他事情(比如看书、喝咖啡),但你真正等待的是广播通知这个特定事件。
C++标准库中的future就类似于这种事件通知机制。当线程需要等待某个特定事件的结果时,可以通过future来获取这个期望的结果。线程可以周期性地检查future状态,或者在等待期间执行其他任务。
future的类型
C++标准库在<future>头文件中提供了两种future类型:
std::future<>:独占式future,只能与一个特定事件关联std::shared_future<>:共享式future,可以与多个事件关联
这两种future的关系类似于std::unique_ptr和std::shared_ptr。当事件与数据无关时,可以使用std::future<void>和std::shared_future<void>特化版本。
使用std::async获取后台任务结果
基本用法
std::async是启动异步任务的简单方式,它返回一个std::future对象,该对象持有最终计算结果。当需要结果时,调用future的get()成员函数会阻塞线程直到结果就绪。
#include <future>
#include <iostream>
int calculate_answer();
void do_other_work();
int main() {
std::future<int> answer = std::async(calculate_answer);
do_other_work();
std::cout << "The answer is " << answer.get() << std::endl;
}
参数传递
std::async支持多种参数传递方式:
struct X {
void foo(int, const std::string&);
std::string bar(const std::string&);
};
X x;
// 成员函数调用
auto f1 = std::async(&X::foo, &x, 42, "hello");
auto f2 = std::async(&X::bar, x, "goodbye");
// 函数对象调用
struct Y { double operator()(double); };
Y y;
auto f3 = std::async(Y(), 3.141);
auto f4 = std::async(std::ref(y), 2.718);
启动策略
可以通过额外参数指定任务的启动策略:
auto f1 = std::async(std::launch::async, Y(), 1.2); // 新线程执行
auto f2 = std::async(std::launch::deferred, baz, std::ref(x)); // 延迟执行
auto f3 = std::async(std::launch::deferred | std::launch::async, baz, std::ref(x)); // 由实现选择
auto f4 = std::async(baz, std::ref(x)); // 默认策略
std::packaged_task:将任务与future关联
std::packaged_task<>将future与函数或可调用对象绑定。当调用packaged_task对象时,会调用相关函数,并将返回值存储在future中。
基本用法
template<>
class packaged_task<std::string(std::vector<char>*,int)> {
public:
template<typename Callable>
explicit packaged_task(Callable&& f);
std::future<std::string> get_future();
void operator()(std::vector<char>*,int);
};
实际应用示例
图形界面编程中,通常需要特定线程执行界面更新操作。使用std::packaged_task可以方便地将任务传递给界面线程:
std::mutex m;
std::deque<std::packaged_task<void()>> tasks;
void gui_thread() {
while(!gui_shutdown_message_received()) {
get_and_process_gui_message();
std::packaged_task<void()> task;
{
std::lock_guard<std::mutex> lk(m);
if(tasks.empty()) continue;
task = std::move(tasks.front());
tasks.pop_front();
}
task();
}
}
template<typename Func>
std::future<void> post_task_for_gui_thread(Func f) {
std::packaged_task<void()> task(f);
std::future<void> res = task.get_future();
std::lock_guard<std::mutex> lk(m);
tasks.push_back(std::move(task));
return res;
}
std::promise:显式设置值
std::promise<T>提供了一种设置值的方式,这个值会与std::future<T>对象关联。当promise设置值后,关联的future状态变为就绪。
网络连接处理示例
void process_connections(connection_set& connections) {
while(!done(connections)) {
for(connection_iterator connection = connections.begin(), end = connections.end();
connection != end; ++connection) {
if(connection->has_incoming_data()) {
data_packet data = connection->incoming();
std::promise<payload_type>& p = connection->get_promise(data.id);
p.set_value(data.payload);
}
if(connection->has_outgoing_data()) {
outgoing_packet data = connection->top_of_outgoing_queue();
connection->send(data.payload);
data.promise.set_value(true);
}
}
}
}
异常处理
future机制也支持异常传递:
double square_root(double x) {
if(x < 0) throw std::out_of_range("x < 0");
return sqrt(x);
}
// 异步调用
std::future<double> f = std::async(square_root, -1);
try {
double y = f.get(); // 这里会抛出异常
} catch(std::out_of_range& e) {
// 处理异常
}
也可以通过promise显式设置异常:
std::promise<double> some_promise;
try {
some_promise.set_value(calculate_value());
} catch(...) {
some_promise.set_exception(std::current_exception());
}
多线程等待:std::shared_future
std::future是独占的,不允许多个线程同时等待同一个事件。std::shared_future解决了这个问题,它是可拷贝的,允许多个对象引用同一个期望值。
使用示例
std::promise<int> p;
std::future<int> f(p.get_future());
std::shared_future<int> sf(std::move(f)); // 转移所有权
// 或者直接构造
std::shared_future<int> sf(p.get_future());
// 使用share()方法
auto sf = p.get_future().share();
总结
C++的future机制为并发编程提供了强大的工具,主要包括:
std::async:简单的异步任务启动方式std::packaged_task:将任务与future关联std::promise:显式设置值或异常std::shared_future:支持多线程等待同一结果
理解并熟练运用这些工具,可以编写出更高效、更安全的并发程序。future机制特别适合那些需要获取异步操作结果的场景,如并行计算、网络编程、GUI编程等。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



