C++ 多线程5 - <future>

  • C++98标准中并没有线程库的存在。
  • C++11中才提供了多线程的标准库,提供了threadmutexcondition_variableatomicfuture等相关对象及功能功能。


概述

  • #include <future> 该头文件是线程支持库的一部分。

  • 该头文件包提供了线程之间异步的相关对象及函数,如下图所示:
    在这里插入图片描述

  • std::future是一个模板类。future对象提供访问异步操作结果的机制。

  • #include <future>中包含两个模板类 std::futurestd::shared_future


1. std::future

  • std::future 对象在内部存储一个将来会被赋值的值,并提供了一个访问该值的机制。其类模板声明为:
template <class T>  future;
template <class R&> future<R&>;     // specialization : T is a reference type (R&)
template <>         future<void>;   // specialization : T is void
  • 有效的 future 是与共享状态(shared state)关联的 future 对象。调用std::asyncstd::packaged_taskstd::promise 能构造并初始化一个 std::future 对象。
  • std::future 成员函数如下:
    在这里插入图片描述

1.1 构造函数、vaild()

  • std::future 构造函数声明如下所示:
future() noexcept;					// 1. 构造有个空的future对象,此对象共享状态为 false
future(const future&) = delete;		// 2. copy [deleted]
future(future&& x) noexcept;		// 3. move,(共享状态为有效的future对象)
  • valid(): 返回 future 对象是否拥有共享状态
           1. 对于使用默认构造产生的 future 对象,该函数返回 false
           2. 通过调用std::asyncstd::packaged_taskstd::promise 来初始化 future 对象。该函数返回 true。
           3. 一旦调用了std::future::get() 函数,再调用此函数将返回 false。

   测试代码:

#include <iostream>       // std::cout
#include <future>         // std::async, std::future
#include <utility>        // std::move

int get_value() { 
    return 10; 
}

int main (){
    std::future<int> foo,bar;
    foo = std::async(get_value);                            

    std::cout << "foo.valid:" << foo.valid() << std::endl;  // 1. foo 通过std::async 创建, true
    std::cout << "bar.valid:" << bar.valid() << std::endl;  // 2. bar 通过默认构造创建, false

    bar = std::move(foo);
    std::cout << std::endl;
    std::cout << "foo.valid:" << foo.valid() << std::endl;  // 1. foo对象转移给bar,foo为空的future对象
    std::cout << "bar.valid:" << bar.valid() << std::endl;  // 2. bar.valid() 为true

    std::cout << "bar's value: " << bar.get() << std::endl;
    std::cout << "bar.valid:" << bar.valid() << std::endl;  // 3. 调用get后,为false

    return 0;
}

foo.valid:1
bar.valid:0

foo.valid:0
bar.valid:1
bar’s value: 10
bar.valid:0

1.2 get()、operator=

  • get() 函数声明
T get();					// 1.
R& future<R&>::get();       // 2. T is references
void future<void>::get();   // 3. T is void
  • get()函数解释
       1. 当共享状态就绪时,返回存储在共享状态中的值。
       2. 如果共享状态尚未就绪(即提供者尚未设置其值或异常),则该函数将阻塞调用的线程直到就绪。
       3. 一旦共享状态就绪,函数就会取消阻塞并返回(或抛出)释放其共享状态。这使得future对象不再有效:对于每个future共享状态,get()最多只能调用一次。
       4. void版本 不返回任何值,但仍然等待共享状态就绪并释放它。

  • get()返回值
       1. 默认版本返回值为std::move(x),x为共享状态的值。
       2. references版本,返回对存储在共享状态中的值的引用。
       3. void版本 无返回值。

   测试代码

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

double func_sum(const double a, const double b){
    double c = a + b;
    std::this_thread::sleep_for(std::chrono::seconds(3));   // 延迟3秒返回
    return c;
}

int main() {
    double num_a = 2.3;
    double num_b = 6.7;
    future<double> fu = async(func_sum, num_a, num_b);		//创建异步线程线程,并将线程的执行结果用fu占位;

    std::cout << "正在进行计算,请您耐心等待." << std::endl;


    // get()阻塞当前,直至异步线程 return
    // future对象的get()方法只能调用一次。
    std::cout << "计算结果:" << fu.get() << std::endl;

    return 0;
}

正在进行计算,请您耐心等待.
计算结果:9

  • operator=() 函数声明
future& operator=(future&& rhs) noexcept; 		// 1. move 的赋值可用
future& operator=(const future&) = delete;		// 2. copy 不可用
#include <iostream>       // std::cout
#include <future>         // std::async, std::future

int get_value() { return 10; }

int main() {
	std::future<int> fut;           // future 默认构造
	fut = std::async (get_value);   // 移动赋值
	
	std::cout << "value: " << fut.get() << '\n';
	
	return 0;
}

value: 10

1.3 wait_*()

  • <1>. wait(): 等待共享状态就绪。如果共享状态尚未就绪(即,提供程序尚未设置其值或异常),则函数将阻塞调用线程,并等待其就绪,一旦共享状态就绪,函数就取消阻塞并返回。
void wait() const;

   测试代码:

#include<iostream>
#include<thread>
#include<chrono>
#include<future>
bool is_prime (int x) {
    for (int i=2; i<x; ++i) {
        if (x%i == 0) 
            return false;
    }
        
    std::this_thread::sleep_for(std::chrono::seconds(3)); // 延迟3秒
    return true;
}

int main() {
    std::future<bool> fut = std::async(is_prime, 194232491); 

    std::cout << "checking...\n";
    fut.wait();                         // wait() 保证在等待返回后准备就绪

    std::cout << "\n194232491 ";
    if (fut.get())                      // get() 不阻塞,直接返回共享值
        std::cout << "is prime.\n";
    else
        std::cout << "is not prime.\n";

    return 0;
}

checking…

194232491 is prime.
// 194232491is prime. 同时打印出。

  • <2>. wait_for() 函数: 在时间跨度 rel_time 内等待共享状态就绪, 如果共享状态尚未就绪,则该函数将阻塞调用的线程直到就绪或已达到设置的时间。
  • 函数返回值为future_status类型:
       1. future_status::ready 共享状态已就绪。
       2. future_status::timeout 指定时间内未就绪。
       3. future_status::deferred 共享状态包含了一个延迟函数。
template <class Rep, class Period>
future_status wait_for(const chrono::duration<Rep,Period>& rel_time) const;
  • <3>. wait_until() 函数: 在目标时间点 abs_time 内等待共享状态就绪, 如果共享状态尚未就绪,则该函数将阻塞调用的线程直到就绪或已达到设置的时间。
  • 函数返回值为future_status类型与上述相同。
template <class Clock, class Duration>
future_status wait_until(const chrono::time_point<Clock,Duration>& abs_time) const;
  • 函数wait_until()wait_for()功能相似,此处以wait_for()为例。

   测试代码:

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

bool is_prime (int x) {
  for (int i=2; i<x; ++i) {
      if (x%i==0) 
        return false;
  }
  return true;
}

int main () {
    std::future<bool> fut = std::async (is_prime,700020007); 

    std::cout << "checking, please wait";
    std::chrono::milliseconds span(100);              
    while (fut.wait_for(span)==std::future_status::timeout)   // 等待 100 毫秒,未就绪
        std::cout << '.';                                     // 便打印一个 .

    bool x = fut.get();

    std::cout << "\n700020007 " << (x?"is":"is not") << " prime.\n";

    return 0;
}

checking, please wait…
700020007 is prime.


2. std::Promise

  • Promise 是一个模板类,可以存储一个T类型的值,以便 future 对象(可能在另一个线程中)检索,从而提供一个同步点。
  • 在构造函数中,Promise 对象与 future 对象相关联,在该状态下,他们可以存储类型为 T 的值。
  • 可通过调用 get_future()Promise 对象与 future 对象相关联,关联后两个对象共享相同的 shared state
         1. Promise 对象是异步提供对象,将会在某一时刻为共享状态设置值。
         2. future 对象是一个异步返回对象,可检查共享状态是否就绪,并可以获取共享状态的值。
  • 此外,共享状态的生命周期可保持至 与之关联的最后一个对象销毁为止。
template <class T>  promise;
template <class R&> promise<R&>;     // specialization : T is a reference type (R&)
template <>         promise<void>;   // specialization : T is void

   std::Promise成员函数如下:
在这里插入图片描述

2.1 构造函数、get_future()

  • 构造函数如下:
promise();											// 1. default, 共享状态为空
template <class Alloc> promise(allocator_arg_t aa,
							   const Alloc& alloc);	// 2. with allocator
promise(const promise&) = delete;					// 3. copy-delete
promise(promise&& x) noexcept;						// 4. move,转移x对象的共享状态
  • get_future() 函数返回与promise对象共享状态关联的future对象,该future对象只能访问绑定的共享状态值和状态,其声明如下:
future<T> get_future();

   测试代码:

void print_int(std::future<int>& fut) {

    std::cout << "已进入线程th." << std::endl;
    int x = fut.get();                              // 3. 获取数据,阻塞
    std::cout << "value: " << x << '\n';
}

int main () {
    std::promise<int> foo;
    std::promise<int> bar = std::promise<int>(std::allocator_arg,std::allocator<int>());

    std::future<int> fut = bar.get_future();        // 1. future、promise对象相关联
    std::thread th(print_int, std::ref(fut));       // 2. fut 作为线程th 的参数

    std::this_thread::sleep_for(std::chrono::seconds(3));
    bar.set_value(20);

    th.join();

    return 0;
}

已进入线程th.
value: 20

  • 阻塞3秒后,打印 val 值。

2.2 operator=、

  • <1>. operator= :转移共享状态,当前对象之前的转移共享将被摧毁。
  • 若参数对象为左值或没有共享状态,任何影响该参数共享状态的操作都将报错。
promise& operator=(promise&& rhs) noexcept;			// 1. 右值赋值ok
promise& operator=(const promise&) = delete;

  • <2>. set_value() : 将val存储为共享状态中的值,该状态变为就绪状态。
  • 如果有与之关联的future对象正在等待对future::get的调用,该函数将取消阻塞并返回val
  • void版本专门化的成员只是使共享状态就绪,而不设置任何值。
void set_value(const T& val);			// 1. 
void set_value(T&& val);		

void promise<R&>::set_value(R& val);   // 2. specializations
void promise<void>::set_value(void);  
  • <3>. set_exception(): 将异常指针p存储在共享状态,该状态变为就绪状态。
  • 如果有与之关联的future对象正在等待对future::get的调用,该函数将取消阻塞并抛出p所指的异常对象。

   测试代码:

#include <iostream>       // std::cin, std::cout, std::ios
#include <functional>     // std::ref
#include <thread>         // std::thread
#include <future>         // std::promise, std::future
#include <exception> 

void get_int(std::promise<int>& prom) {             // 1.参数为 promise
    int x;
    std::cout << "Please, enter an integer value: ";
    std::cin.exceptions(std::ios::failbit);         // 设置failbit
    try {
        std::cin >> x;
        prom.set_value(x);
    }
    catch (std::exception&) {
        prom.set_exception(std::current_exception());
    }
}

void print_int(std::future<int>& fut) {             // 2.参数为 future
    try {
        int x = fut.get();
        std::cout << "value: " << x << '\n';
    }catch (std::exception& e) {
        std::cout << "[exception caught: " << e.what() << "]\n";
    }
}

int main () {
    std::promise<int> prom;
    std::future<int> fut = prom.get_future();

    std::thread th1(print_int, std::ref(fut));      // 3. 该线程获取 共享状态值
    std::thread th2(get_int, std::ref(prom));       // 4. 该线程设置 共享状态值

    th1.join();
    th2.join();

    return 0;
}

Please, enter an integer value: 10
value: 10


3. std::packaged_task

  • std::packaged_task 类模板和 std::promise 十分相似。其名称直翻为打包任务,其功能为:打包的任务包装一个可调用元素,并允许异步检索其结果。
template <class T> packaged_task;     // undefined
template <class Ret, class... Args> class packaged_task<Ret(Args...)>;
  • std::packaged_taskstd::function很类似,但是会自动将结果传输给 std::future对象。std::packaged_task 对象内部包含两个元素:
        1. 存储的任务,它是一些可调用的对象(如函数指针、指向成员的指针或函数对象),并返回Ret类型的值。
        2. 共享状态,存储调用存储的任务的结果,并且可以通过 std::future对象异步访问。
  • std::packaged_task 共享状态通过调用成员函数 get_futurefuture对象相关联。调用后,两个对象共享相同的共享状态:
        1. 打包的任务对象是异步provider,通过调用存储的任务,可以在某个时刻将共享状态设置为就绪。
        2. future对象是一个异步返回对象,它可以检索共享状态的值,并在必要时等待它准备就绪。

  
   成员函数std::packaged_task 所包含成员函数如下所示:

3.1 构造函数、get_future

  • std::packaged_task 构造函数如下
packaged_task() noexcept;					// 1. default, 无共享状态、无任务

template <class Fn>
explicit packaged_task(Fn&& fn);			// 2. init,有共享状态,任务为fn

template <class Fn, class Alloc>			// 3. with allocator
explicit packaged_task(allocator_arg_t aa, const Alloc& alloc, Fn&& fn);

packaged_task(packaged_task&) = delete;		// 4. copy [deleted]
packaged_task(packaged_task&& x) noexcept;	// 5. move
  • 上述函数声明中, fn 为任务函数、Args 为输入参数。
  • future<Ret> get_future();: 返回与对象的共享状态关联的 future 对象。
  • 一旦调用了packaged_task的存储任务,返回的future对象就可访问该任务在共享状态上设置的值或异常。

   测试代码:

#include <iostream>     // std::cout
#include <utility>      // std::move
#include <future>       // std::packaged_task, std::future
#include <thread>       // std::thread

int main () {
    std::packaged_task<int(int)> foo;                           // 1. 默认构造
    std::packaged_task<int(int)> bar([](int x){return x*2;});   // 2. initialized, fn为 lamda-func

    foo = std::move(bar);                                       // 3. 移动构造

    std::future<int> ret = foo.get_future();                    // 4. get-future

    std::thread(std::move(foo),10).detach();                    // 创建并、分离线程

    // ...

    int value = ret.get();                                      // 5. 阻塞,等待任务完成并返回共享状态值
    std::cout << "The double of 10 is " << value << ".\n";

    return 0;
}

The double of 10 is 20.

  • 上述代码中 std::packaged_task<int(int)> 表示任务返回类型为 int、任务参数为一个类型为 int 的参数。
  • 因为返回类型为 int,所有future对象的类型为 std::future<int>。这两句可以该为:
using T = std::function<int(int)>; // or typedef std::function<int(int)> T;
std::packaged_task<T> foo; 

3.2 operator=、operator()

  • <1> operator=:移动赋值,获取参数对象 rhs 的共享状态和存储任务。其它方面 promise::operator=相似。
packaged_task& operator= (packaged_task&& rhs) noexcept;  		// 1. move
packaged_task& operator= (const packaged_task&) = delete;		// 2. copy [deleted]

  • <2> operator() 实现仿函数功能:
void operator()(Args... args);
  • operator() 可调用并执行当前对象存储的任务,将args转发为参数:
        1. 如果成功调用任务并完成,则它返回的值将存储在共享状态中。
        2. 如果对存储任务的调用引发异常,则会捕获异常并将其存储在共享状态中。

在这两种情况下,共享状态都已就绪(这将取消阻止当前正在等待它的所有线程)。
共享状态可以通过相关联的future对象调用get来访问。

  • <3> reset():在保持相同存储任务的同时,以新的共享状态重置packaged_task对象。
        1. 这允许再次调用存储的任务。
        2. 在内部,函数的行为就像move分配了一个新构造的打包任务。

   测试代码:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <chrono>
#include <future>

using namespace std;
void task_lambda() {
    // 使用 packaged_task 包装任务
    packaged_task<int(int,int)> task([](int a, int b){ return a + b;});

    std::future<int> result = task.get_future();        // 1. 关联future对象
    task(2, 10);                                        // 2. 仿函数形式,启动任务

    cout << "task_lambda :" << result.get() << "\n";
    cout << "future.valid:" << result.valid() << endl;

    task.reset();                                       // 3. 重置共享状态,如果没这句,下一句将报错
    result = task.get_future();
    cout << "future.valid:" << result.valid() << endl;

    std::thread thread_(move(task), 2, 10);             // 4. 通过线程启动任务,异步启动
    thread_.join();
    cout << "task_thread :" << result.get() << "\n";
}

int main() {
    task_lambda();
    return 0;
}
  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
可以使用`std::future`来获取线程池中任务的返回值。在`enqueue`函数中,任务被包装在`std::packaged_task`中,这个包装器可以将一个函数包装成一个可调用对象,并且可以使用`std::future`来获取函数的返回值。 在`enqueue`函数中,我们使用`std::make_shared`创建了一个`std::packaged_task`,并将要执行的任务`f`和其参数`args`绑定在一起。然后,我们将这个`std::packaged_task`封装在一个`std::shared_ptr`中,以便可以在其他线程中访问它。 接下来,我们使用`std::future`获取`std::packaged_task`的返回值。`std::future`是一个异步结果的占位符,可以用来检查任务是否已经完成,并且可以获取任务的返回值。 具体地,我们可以在调用`enqueue`函数后,使用返回的`std::future`对象的`get()`函数来获取任务的返回值。`get()`函数会阻塞当前线程,直到任务执行完毕并返回结果。 例如,假设我们要执行一个函数`int add(int x, int y)`,我们可以使用以下方式来获取其结果: ```c++ ThreadPool pool(4); // 创建线程池,有4个线程 // 将任务加入线程池,并获取返回值的future对象 auto result = pool.enqueue(add, 3, 4); // 等待任务执行完成,并获取返回值 int res = result.get(); std::cout << "3 + 4 = " << res << std::endl; ``` 这里的`result.get()`会阻塞当前线程,直到任务执行完毕并返回结果。最后,我们将`res`输出到控制台。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值