C++并发编程(III)

Posted on Sun 21 February 2021 in programming

我们知道std::thread会创新一个新进程并执行传入的函数。但是std::thread没有提供从线程函数中获取返回值的直接方式。如果通过传参的方式获取线程的返回值, 这在多线程环境中可能会出现数据竞争的问题,需要合理使用互斥锁或其他同步手段防止这种情况。因此c++提供了std::asyncstd::future来简化这种问题。

std::async和std::future

std::async是C++11标准库中用于处理异步任务的函数。这个函数的主要目的是为了简化并发并尽可能地让并发变得易于使用。具体来说,std::async函数允许我们在独立的线程上异步地执行函数或可调用对象,并返回一个std::future对象,我们可以通过这个对象获取这个函数的返回值。

那什么是std::future对象呢? 在C++中,std::future是一个模板类,它表示一个异步操作的结果。你可以使用 std::future 来获取在另一个线程或异步任务中计算的值,而且没有必要立即获取这个值,你可以在稍后的某个时刻获取。

#include <iostream>
#include <future>

// 移动一个函数的执行到一个单独的线程,并获取它的返回值
int compute() {
    return 2 * 2;
}

int main() {
    // 创建一个异步任务
    std::future<int> result = std::async(compute);

    // 在这里可以做其他的事情...

    // 从异步任务获取返回值
    auto computed_value = result.get();
    std::cout << "Computed value: " << computed_value << std::endl;

    return 0;
}

比如我们就使用std::async启动另一个线程异步执行compute函数,然后调用get()成员函数获取计算的结果,如果计算没有完成,程序会在此等待直到获取结果。

std::promise对象

std::promise是C++标准库中一个用于异步编程的模板类,它可以很方便地在一个线程中存储一个值或异常,并能让另一个线程访问这个值或异常。 简单来说,你可以把std::promise想象成一个用于异步操作的管道的一端,而这个管道的另一端则是std::future对象。 一个std::promise对象包含一个共享的共享状态,这个状态可以包含一个值或一个异常。 可以通过调用std::promise::set_value或者std::promise::set_exception方法来设置这个共享状态。这两个方法都会将共享状态标记为ready,同时通知任何等待这个std::promise对象的std::future对象。 下面是一个示例,展示了如何使用std::promise对象来传递一个计算结果从一个线程到另一个线程:

#include <iostream>
#include <thread>
#include <future>

void calculate(std::promise<int> intPromise) {
    int result = 10; // 做一些计算
    intPromise.set_value(result); // 将结果设置到promise对象中
}

int main()
{
    // 创建一个std::promise对象
    std::promise<int> prom;

    // 获取与promise相关联的future对象
    std::future<int> fut = prom.get_future();

    // 启动一个新线程,并将promise移动到新线程中
    std::thread t(calculate, std::move(prom));

    // 获取计算结果
    std::cout << "The result is " << fut.get() << std::endl;

    // 等待线程完成
    t.join();

    return 0;
}

std::packaged_task对象

std::packaged_task是一个类模板,它是一个可调用对象包装器,用来封装类似函数的可调用目标(包括函数、Lambda表达式等),并且它可以有返回值。当你调用std::packaged_task对象时,它会调用被包装的函数,并存储该函数的返回值或捕获到的异常。你可以通过一个std::future对象获取这个结果,或等待它被计算出来。

#include <iostream>
#include <future>
#include <thread>
#include <chrono>


int main() {
    using namespace std::chrono_literals;

    // 将一个返回值为7的 lambda 表达式封装到 task 中
    // std::packaged_task 的模板参数为要封装函数的类型
    std::packaged_task<int()> task([](){
        const auto start = std::chrono::high_resolution_clock::now();

        std::this_thread::sleep_for(2000ms);
        const auto end = std::chrono::high_resolution_clock::now();
        auto elapsed = end - start;
        std::cout << "Waited " << elapsed.count() / 1e6 << "ms \n";

        return 7;
    });
    std::future<int> result = task.get_future(); // 获得 task 的期物
    std::thread(std::move(task)).detach(); // 在一个线程中执行 task
    std::cout << "waiting...";
    result.wait(); // 在此设置屏障,阻塞到期物的完成

    // 输出执行结果
    std::cout << "done!" << std:: endl << "future result is "
              << result.get() << std::endl;
    return 0;
}

Reference: 1. (现代C++教程)[https://changkun.de/modern-cpp/zh-cn/07-thread/#7-3-%E6%9C%9F%E7%89%A9]