深入理解C++并发编程中的future机制

深入理解C++并发编程中的future机制

前言

在现代C++并发编程中,future是一种非常重要的同步机制,它允许我们异步获取任务执行结果。本文将深入探讨C++标准库中的future机制,帮助读者全面理解其工作原理和使用方法。

future的基本概念

想象这样一个场景:你去交通枢纽乘飞机,办理完手续后需要在候机区等待通知。在这段等待时间里,你可以做其他事情(比如看书、喝咖啡),但你真正等待的是广播通知这个特定事件。

C++标准库中的future就类似于这种事件通知机制。当线程需要等待某个特定事件的结果时,可以通过future来获取这个期望的结果。线程可以周期性地检查future状态,或者在等待期间执行其他任务。

future的类型

C++标准库在<future>头文件中提供了两种future类型:

  1. std::future<>:独占式future,只能与一个特定事件关联
  2. std::shared_future<>:共享式future,可以与多个事件关联

这两种future的关系类似于std::unique_ptrstd::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机制为并发编程提供了强大的工具,主要包括:

  1. std::async:简单的异步任务启动方式
  2. std::packaged_task:将任务与future关联
  3. std::promise:显式设置值或异常
  4. std::shared_future:支持多线程等待同一结果

理解并熟练运用这些工具,可以编写出更高效、更安全的并发程序。future机制特别适合那些需要获取异步操作结果的场景,如并行计算、网络编程、GUI编程等。

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值