[C++11 多线程异步] --- std::promise/std::future

Posted Overboom

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[C++11 多线程异步] --- std::promise/std::future相关的知识,希望对你有一定的参考价值。

1 为什么需要异步

C++11 中增加的线程类,使得我们能够非常方便的创建和使用线程,但有时会有些不方便,比如需要获取线程返回的结果,就不能通过 join() 得到结果,只能通过一些额外手段获得,下面给出具体实现代码:

#include <vector>
#include <numeric>
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>

int res = 0;						//保存结果的全局变量
std::mutex mu;						//互斥锁全局变量
std::condition_variable cond;       //全局条件变量
 
void accumulate(std::vector<int>::iterator first,
                std::vector<int>::iterator last)

    int sum = std::accumulate(first, last, 0);      //标准库求和函数
    std::unique_lock<std::mutex> locker(mu);
    res = sum;
    locker.unlock();
    cond.notify_one();              // 向一个等待线程发出“条件已满足”的通知

 
int main()

    std::vector<int> numbers =  1, 2, 3, 4, 5, 6 ;
    std::thread work_thread(accumulate, numbers.begin(), numbers.end());

    std::unique_lock<std::mutex> locker(mu);
    cond.wait(locker, []() return res;);   //如果条件变量被唤醒,检查结果是否被改变,为真则直接返回,为假则继续等待
    std::cout << "result=" << res << '\\n';
    locker.unlock();
    work_thread.join();         //阻塞等待线程执行完成
    
    return 0;

上面代码定义一个全局变量,在子线程中赋值,在主线程中读这个变量的值,整个过程比较繁琐,
虽然也实现了获取异步任务执行结果的功能,但是需要的全局变量较多,多线程间耦合度也高。
鉴于此,C++ 11新增了一个< future >库函数为异步编程提供了很大的便利。

2 异步编程的概念

多线程异步就是一个线程发起请求后,不等待这个发起的请求返回任何响应就去先干别的事,当然最后是等待到这个返回呢还是不等呢?关键就是要看,是否真的返回,如果返回了,则接受,不返回,也不会一直等待,遇到main函数结束时,操作系统会结束并清理这个进程的所有资源和痕迹。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

3 C++异步编程介绍

< future >头文件功能允许对特定提供者设置的值进行异步访问,可能在不同的线程中。
这些提供程序(要么是promise 对象,要么是packaged_task对象,或者是对异步的调用async与future对象共享共享状态:提供者使共享状态就绪的点与future对象访问共享状态的点同步。

3.1 std::future

std::future可以被 std::async,std::packaged_task,std::promise创建,并用于异步获得 std::async,std::packaged_task,std::promise等对象设置的值或异常。
std::future 是一个模板类,也就是这个类可以存储任意指定类型的数据

// 定义于头文件 <future>
template< class T > class future;
template< class T > class future<T&>;
template<>          class future<void>;

成员函数

函数说明
构造函数-
析构函数-
operator=-
get获取结果
validchecks if the future has a shared state
wait阻塞等待结果
wait_for等待结果可用(指定等待时间)
wait_until等待结果可用(指定等待时刻)

3.2 std::promise

3.2.1 promise类成员函数

std::promise是一个模板类,需要在线程中传递什么类型数据,模板参数就指定为什么类型

emplate< class R > class promise;
template< class R > class promise<R&>;
template<>          class promise<void>;

成员函数

函数说明
构造函数-
析构函数-
operator=-
swap交换两个promise对象
get_future绑定并返回一个future对象
set_value设置值
set_value_at_thread_exit在线程退出时设置值
set_exception设置异常
set_exception_at_thread_exit在线程退出时设置异常

3.3 使用promise与future传递结果

通过 promise 传递数据的过程一共分为 5 步:

  • 在主线程中创建 std::promise 对象
  • 将这个 std::promise 对象通过引用的方式传递给子线程的任务函数
  • 在子线程任务函数中给 std::promise 对象赋值
  • 在主线程中通过 std::promise 对象取出绑定的 future 实例对象
  • 通过得到的 future 对象取出子线程任务函数中返回的值。

下面看一段代码示例:

#include <iostream>
#include <thread>
#include <future>
using namespace std;

int main()

    promise<int> pr;
    thread t1([](promise<int> &p) 
        p.set_value(100);
        this_thread::sleep_for(chrono::seconds(3));
        cout << "睡醒了...." << endl;
    , ref(pr));

    future<int> f = pr.get_future();
    int value = f.get();
    cout << "value: " << value << endl;

    t1.join();
    return 0;

示例程序的中子线程的任务函数指定的是一个匿名函数,在这个匿名的任务函数执行期间通过 p.set_value(100); 传出了数据并且激活了状态,数据就绪后,外部主线程中的 int value = f.get(); 解除阻塞,并将得到的数据打印出来,5 秒钟之后子线程休眠结束,匿名的任务函数执行完毕。

3.4 std::packaged_task

std::promise通过set_value可以使得与之关联的std::future获取数据。本篇介绍的std::packaged_task则更为强大,它允许传入一个函数,并将函数计算的结果传递给std::future,包括函数运行时产生的异常。

package_task也是一个模板类,模板类型和要在线程中传出的数据类型是一致的。

// 定义于头文件 <future>
template< class > class packaged_task;
template< class R, class ...Args >
class packaged_task<R(Args...)>;

构造函数

// ①
packaged_task() noexcept;
// ②
template <class F>
explicit packaged_task( F&& f );
// ③
packaged_task( const packaged_task& ) = delete;
// ④
packaged_task( packaged_task&& rhs ) noexcept;

构造函数①:无参构造,构造一个无任务的空对象
构造函数②:通过一个可调用对象,构造一个任务对象
构造函数③:显示删除,不允许通过拷贝构造函数进行对象的拷贝
构造函数④:移动构造函数

常用public成员函数
通过调用任务对象内部的 get_future() 方法就可以得到一个 future 对象,基于这个对象就可以得到传出的数据了。

std::future<R> get_future();

3.5 使用packaged_task与future传递结果

#include <iostream>
#include <thread>
#include <future>
using namespace std;

int main()

    packaged_task<int(int)> task([](int x) 
        return x += 100;
    );

    thread t1(ref(task), 100);

    future<int> f = task.get_future();
    int value = f.get();
    cout << "value: " << value << endl;

    t1.join();
    return 0;

在上面的示例代码中,通过 packaged_task 类包装了一个匿名函数作为子线程的任务函数,最终的得到的这个任务对象需要通过引用的方式传递到子线程内部,这样才能在主线程的最后通过任务对象得到 future 对象,再通过这个 future 对象取出子线程通过返回值传递出的数据。

以上是关于[C++11 多线程异步] --- std::promise/std::future的主要内容,如果未能解决你的问题,请参考以下文章

[C++11 多线程异步] --- std::promise/std::future

[C++11 多线程异步] --- std::promise/std::future

C++11 多线程 asyncfuturepackaged_taskpromise

精通C#---多线程,并行,异步编程

Java多线程11-异步编程

C++11 线程与异步性能(VS2013)