Bootstrap

c++11 多线程之future和promise

C++ 系列



前言

编写多线程时,无法避免的要进行线程间数据同步/通信。std::promise 和 std::future 是 C++ 进行单向数据传递的一种方式。future和promise的作用是在不同线程之间传递数据。std::promise 是数据的输入端,std::future 是数据的输出端。


一、std::future是什么?

C++11中提供异步创建多线程的工具。我们想要从线程中返回异步任务结果,一般需要依靠全局变量;从安全角度看,有些不妥;为此C++11提供了std::future类模板,future对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。
future对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果

std::async、std::packaged_task或std::promise能提供一个std::future对象给该异步操作的创建者,异步操作的创建者能用各种方法查询、等待或从 std::future 提取值。
若异步操作仍未提供值,则这些方法可能阻塞。异步操作准备好发送结果给创建者时,通过接口(eg,std::promise::set_value std::future) 修改共享状态的值。

在C++标准库中,有两种“期望”,使用两种类型模板实现,
唯一期望(unique futures,std::future<>) std::future的实例只能与一个指定事件相关联。
共享期望(shared futures)(std::shared_future<>) std::shared_future的实例就能关联多个事件。

二、函数详解

1. std::promise

std::promise本身是一个模版类,通过std::promise可以像数据管道中设置value,异常和相应的通知信息。
如果std::promise设置值或者异常超过一次,那么会抛出std::future_error异常。
std::promise提供了如下接口

get_future()               // 返回相应的future
set_value()                // 设置一个相应的值
set_exception()            // 设置一个异常
set_value_at_thread_exit() // 设置一个值,当线程退出时,promise使数据管道中相应值的状态变为ready
set_exception_at_thread_exit()

2. std::future

std::future本身是一个模版类,且其是不可拷贝对象, 可类比std::unique_ptr, 对其所管理的资源具有独占权。
通过std::future,你可以
1、从std::promise获得相应的值
2、询问std::promise是否将值设置为可用
3、等待std::promise的通知。这个等待可以设置一个相对时间间隔或者一个绝对时间
4、创建一个共享的future(std::shared_future)

std::future提供了如下主要接口

share()                         // 返回一个std::shared_future
get()                           // 从数据管道获得一个值或者异常,多次调用会导致程序异常。
valid()                         // 检查共享状态是否有效,当调用完get接口后,该函数返回false
wait()                          // 等待结果
wait_for()                      // 等待结果,这个接口会等待一个时间间隔,其返回值为std::future_status
wait_until()                    // 等待结果,该接口会等待到一个绝对时间点,返回值为std::future_status

3 std::future_status

future的wait_for和wait_until接口的返回值为std::future_status. 在C++11标准中,该值定义如下
ready: 数据管道中的结果已经ready,也即可用
timeout: 数据管道中的数据不可用,且wait等待时间已经到达
deferred: 接口被延迟执行,也即还未开始

enum class future_status {
	ready,
	timeout,
	deferred
};

4 std::async

将异步操作用std::packaged_task包装起来,然后将异步操作的结果放到std::promise中,这个过程就是创造未来的过程。外面再通过future.get/wait来获取这个未来的结果。

async原型:

template<class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&...args);

fn接收一个可调用对象(仿函数、lambda表达式、类成员函数、普通函数…)作为参数,并且异步或是同步执行他们。
policy指的异步执行还是同步执行,由第一个参数的执行策略决定:
1、std::launch::async 传递的可调用对象异步执行;
2、std::launch::deferred 传递的可调用对象同步执行;
3、std::launch::async | std::launch::deferred 可以异步或是同步,取决于操作系统,我们无法控制;
4、如果我们不指定策略,则相当于(3)。
args指可调用对象的入参

std::packaged_task
利用std::packaged_task包装的可调用对象,在异步执行后可通过std::future获取返回值。
一个模板类,std::packaged_task包装任何可调用目标(函数、lambda表达式、bind表达式、函数对象)以便它可以被异步调用。
std::packaged_task类似于std::function,它的返回值或抛出的异常被自动存储于能通过std::future对象访问的共享状态中。

1、std::packaged_task::get_future(),调用std::packaged_task的get_future成员函数返回std::future对象,其与共享状态关联。调用后std::packaged_task与std::future共享相同的共享状态
2、std::packaged_task::operator(), 调用存储的任务。
3、std::packaged_task::reset() 在保持相同存储的任务的同时,以新的共享状态重置对象。

packaged_task原型:

template <class Fn> explicit packaged_task (Fn&& fn);  

std::shared_future
std::shared_future 顾名思义就是多个线程共享一个 std::shared_future,且可以拷贝。可用在一个线程传递数据给多个线程的时候,多个线程在自身的线程空间内通过 std::shared_future 共享一个 future,这是线程安全的。

std::shared_future::get 可以无限次调用,而 std::future::get 仅能调用一次。std::shared_future::get 返回的一定是引用

shared_future 可以通过某个 std::future 对象隐式转换(参见 std::shared_future 的构造函数),或者通过 std::future::share() 显示转换,返回一个 std::shared_future 对象,无论哪种转换,被转换的那个 std::future 对象都会变为 not-valid。

三、代码

std::future与std::promise

#include <iostream>
#include <future>
#include <exception>
#include <thread>
#include <unistd.h>

void setval(std::promise<int>& p ,int a) {
  try {
    if (a == 0) {
        throw std::runtime_error("set value 0 not perimted");
    } else {
        sleep(5);
        p.set_value(a);
    }
  } catch(...) {
    p.set_exception(std::current_exception());
  } 
}
 
int CallFuture1() {
  std::promise<int> prom;
  auto fut = prom.get_future();
  std::thread t1(setval, std::ref(prom), 2);
  try {
    std::cout << "fut get value: " << fut.get() << "\n";
  } catch(std::exception& ptr) {
    std::cout << ptr.what() << "\n";
  }
  t1.join();
  return 0;
}

std::packaged_task
前面在使用时都是调用 std::promise::set_value 来写入值,有时我们可能想将一个函数的返回值写入。当然,我们可以先调用这个函数得到值再调用 std::promise::set_value,但标准库提供了 std::packaged_task 来代替这种做法。

int CallFuture2() {
    auto && task = std::packaged_task<int(int)>([](int a){
        sleep(2);
        return (42+a);
    });
    auto &&future = task.get_future();
    auto t1 = std::thread(std::move(task),2);
    std::cout << future.get() << std::endl;
    t1.join();
    return 0;
}


int CallFuture2_1() {
    auto sleep = []() {
        std::this_thread::sleep_for(std::chrono::seconds(1));
        return 1;
    };
    std::packaged_task<int()> task(sleep);
	auto f = task.get_future();
	task(); 
	std::cout << "You can see this after 1 second"<<std::endl;
	std::cout << f.get() << std::endl;
    return 0;
}


std::async
前面手动创建线程,sync帮创建了线程。

int CallFuture3() {
    auto future = std::async([]{
        sleep(2);
        return 42;
    });
    std::cout << future.get() << std::endl;
    return 0;
}

std::future_status
返回状态有三种可能

int thread_task(std::promise<int>& pro, int i)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    std::cout << "thread_task" << i << std::endl;
    pro.set_value(i);
    return 0;
}
 
int CallFuture4() {
  std::promise<int> pro;
    std::thread mythread(thread_task, std::ref(pro), 5);
    mythread.join();
    std::future<int> fut = pro.get_future();
    std::future_status status =  fut.wait_for(std::chrono::milliseconds(1000));
    if (std::future_status::ready == status){     //线程已成功返回
       
        std::cout<< "ready"<<std::endl;
    }
    else if (std::future_status::timeout == status){ //wait_for的时间已结束,线程仍未成功返回
        std::cout<< "timeout"<<std::endl;
    }
    else if (std::future_status::deferred == status){ //如果std::async的第一个参数设置为std::launch::deferred,则该线程会直到std::future对象调用wait()或get()时才会执行,这种情况就会满足
        std::cout<< "deferred"<<std::endl; 
    }
    std::cout << fut.get() << std::endl;
    return 0;
}



using namespace std::chrono; 
std::string getData(std::string recvData) {
  
    std::cout << "getData start" << std::this_thread::get_id() << std::endl;
    std::this_thread::sleep_for(seconds(5));
    return "DB_" + recvData;
}
int CallFuture4_1() {
    system_clock::time_point start = system_clock::now();
    std::future<std::string> fut = std::async(std::launch::async, getData, "Data");
  
    std::future_status status;
    std::string dbData;
    do
    {
        status = fut.wait_for(std::chrono::seconds(1));
        switch (status){
        case std::future_status::ready:
            std::cout << "Ready..." << std::endl;
            dbData = fut.get();
            std::cout << dbData << std::endl;
            break;
        case std::future_status::timeout:
            std::cout << "timeout..." << std::endl;
            break;
        case std::future_status::deferred:
            std::cout << "deferred..." << std::endl;
            break;
        default:
            break;
        }
    } while (status != std::future_status::ready);
  
    auto end = system_clock::now();
    auto diff = duration_cast<std::chrono::seconds>(end - start).count();
    std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
    return 0;
}

std::shared_future

int CallFuture5()
{
    auto promise = std::promise<int>();
    auto t1 = std::thread([&promise]{
        sleep(2);
        promise.set_value(42);
    });

    auto shared_future = std::shared_future<int>(promise.get_future());
    auto t2 = std::thread([shared_future]{
        std::printf("thread 2: %d\n", shared_future.get());
    });
    std::printf("main: %d\n", shared_future.get());

    t1.join();
    t2.join();
    return 0;
}

int CallFuture5_1 ()
{
    std::future<int> fut = std::async([](){return 10;});
    std::shared_future<int> shared_fut = fut.share();

    // 共享的 future 对象可以被多次访问.
    std::cout << "value: " << shared_fut.get() << '\n';
    std::cout << "its double: " << shared_fut.get()*2 << '\n';

    return 0;
}


总结

通过本文的学习,对future promise有个了一定的认识,希望你能够学而用之。

参考:https://blog.csdn.net/qls315/article/details/125470427

;