[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 | 获取结果 |
valid | checks 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