promise介绍
存在于头文件 future 中,相当于一个在并行环境中的数据存储箱,各线程可以通过这一箱子传递某种类型的数据
举个例子
如果我们要定义一个装int类型的箱子
std::promise<int> boxOfInt
赋值
- 如果我们要往箱子里放东西,比如放一个10
boxOfInt.set_value(10)
箱子同时只能放一个东西,如果还想多放(多次赋值),就会报错,terminate called after throwing an instance of ‘std::future_error’ what(): std::future_error: Promise already satisfied terminate called recursively
放好东西后,箱子就会给自己贴一个标签(共享状态标志变为ready)
这个标签,也可以放的线程结束后再贴
如果我们把东西放进箱子后,
- 并不想马上就把箱子关上(即马上把共享标志变为ready),可以使用
set_value_at_thread_exit(),正如其字面意思,在线程结束后设值
void f(promise<int> &p)
{
p.set_value_at_thread_exit(123);//可以正常使用
}
void main()
{
promise<int> p;
auto re = p.get_future();
thread t(f, std::ref(p));
t.join();
cout << re.get() << endl;
system("pause");
}
注意:如果要使用set_value_at_thread_exit函数,要保证在线程退出时,promise对象不能被销毁
什么场景下会用 std::promise::set_value_at_thread_exit?
想象一下,如果有一个人一直在等另一个人往箱子里放东西,等箱子里有东西之后,它拿到东西就准备过河,但过河的桥还没修好,东西却已经准备好了。如果直接把东西放进箱子,它拿到东西,但桥都没有,跑过去只能掉河里了。
set_value_at_thread_exit就是为了解决这个矛盾,在资源没有准备好的情况下,不告诉别人已经放好东西了(用来阻塞另外一个线程,实现对线程执行顺序和共享资源的控制)
- 当然除了放某种类型的值之外,也可以在里面放异常对象
boxOfException.set_exception(std::current_exception());
同理,也有set_exception_at_thread_exit和set_value_at_thread_exit类似
举例说明
#include <iostream> // std::cin, std::cout, std::ios
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future
#include <exception> // std::exception, std::current_exception
// 函数的目的是 获取用户输入的数据
void get_int(std::promise<int>& prom) {
int x;
std::cout << "Please, enter an integer value: ";
std::cin.exceptions (std::ios::failbit); // throw on failbit
try {
std::cin >> x; // sets failbit if input is not int
prom.set_value(x);
} catch (std::exception&) {
// 当输入的数据不是int类型的,无法装入箱子时,就在里面装一个异常
prom.set_exception(std::current_exception());
}
}
void print_int(std::future<int>& fut) {
try {
int x = fut.get();
std::cout << "value: " << x << '\n';
} catch (std::exception& e) {
// 发现箱子里装进了一个异常,而不是一个数据,输出
// [exception caught: basic_ios::clear: iostream error]
std::cout << "[exception caught: " << e.what() << "]\n";
}
}
int main ()
{
std::promise<int> prom;
std::future<int> fut = prom.get_future();
std::thread th1(get_int, std::ref(prom));
std::thread th2(print_int, std::ref(fut));
th1.join();
th2.join();
return 0;
}
- 更多神奇的赋值操作
传函数指针、可变元函数
https://blog.csdn.net/godmaycry/article/details/72844159
取值
要拿出箱子里的东西
// future之后会讲解
std::future<int> fut = boxOfInt.get_future();
int something = fut.get();
注意了,promise用完一次后,就无法继续使用(get 只能调用一次),如果继续使用会出现,terminate called after throwing an instance of ‘std::future_error’ what(): std::future_error: Future already retrieved
可以想成成程序每次打开promise都是暴力拆箱,拆完箱子就烂了,只能重新给它个新的。
( 箱子是独一无二的,std::promise的普通赋值操作是被禁用的,禁止拷贝)
boxOfInt = std::promise<int>(); // 赋值为一个新的promise对象.
-
如果箱子里没有放东西,那就会拿不出来,程序就会阻塞,等待箱子里有东西可以拿。
-
同样箱子里的东西,如果已经被拿走了,下一个来拿的程序,自然就会什么都拿不到。
下面是一串可以正常运行的代码
#include <iostream> // std::cout
#include <thread> // std::thread
#include <future> // std::promise, std::future
// 创建一个装int类型的箱子
std::promise<int> boxOfInt;
// 这个函数目的就是从箱子里取出东西
void print_global_promise () {
std::future<int> fut = boxOfInt.get_future();
int x = fut.get();
std::cout << "value: " << x << '\n';
}
int main ()
{
std::thread th1(print_global_promise);
// 往箱子里放数据,th1线程就会去拿数据
boxOfInt.set_value(10);
th1.join();
// 箱子被th1弄烂了,只好重新弄一个
boxOfInt = std::promise<int>();
// 同理 th2 也来开箱子
std::thread th2 (print_global_promise);
boxOfInt.set_value (20);
th2.join();
return 0;
}
可以自己尝试注释掉,放东西(set_value),做新箱子等步骤,查看报错的信息。
packaged_task介绍
packaged_task 和 promise 比较类似,只是promise是可以装多种类型的箱子。
而packaged_task 更像是一种promise的一种特化,它是一个专门装函数的箱子,使得函数的调用更加便捷
如果一个promise要装函数
// 定义一个函数
int Test_Fun(int a, int b, int &c)
{
//a = 1, b = 2
c = a + b + 230;
return c;
}
// 声明一个可调对象F
// 等同于typedef std::function<int(int, int, int&)> F;
using F = std::function<int(int, int, int&)>;
std::promise<F> pr1;
// 传入函数Test_Fun
pr1.set_value(std::bind(&Test_Fun, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
// 取出函数
std::future<F> f = pr1.get_future();
auto fun = f.get(); //fun等同于Test_Fun,只是被包装成了lambda函数
// 如果要执行这个函数
int val = 3;
auto result = fun(1,2,val);
可以看出在整个的 装入-取出-执行 的过程还是比较繁琐的
valid
作用是检查package是否和一个有效的共享状态相关联,
可以理解为查看箱子里面是否有东西存在,该成员函数在promise中是不存在的
使用默认构造函数生成的package_task对象,使用valid返回的是一个false
只有使用move赋值,或者swap操作后才会变为true
// packaged_task::get_future
#include <iostream> // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread
// function that launches an int(int) packaged_task in a new thread:
std::future<int> launcher (std::packaged_task<int(int)>& tsk, int arg) {
if (tsk.valid()) {
std::future<int> ret = tsk.get_future();
std::thread (std::move(tsk),arg).detach();
return ret;
}
else return std::future<int>();
}
int main ()
{
std::packaged_task<int(int)> tsk ([](int x){return x*2;});
std::future<int> fut = launcher (tsk,25);
std::cout << "The double of 25 is " << fut.get() << ".\n";
return 0;
}
赋值
而如果要在packaged_task中放函数
// 定义一个函数
int countdown (int from, int to) {
for (int i=from; i!=to; --i) {
std::cout << i << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Finished!\n";
return from - to;
}
// 向packaged_task中放入函数
std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
// 也可以使用 lambda 表达式初始化一个 packaged_task 对象.
std::packaged_task<int(int)> bar([](int x){return x*2;});
// 使用move-赋值
std::packaged_task<int(int)> foo; // 默认构造函数.
foo = std::move(bar); // move-赋值操作,也是 C++11 中的新特性.
package_task 和 promise 都是只能使用 move赋值 的,禁止使用 赋值操作 运算
取值
调用函数(取值准备)
package_task的结果和promise一样,也是通过future返回的,
由于package_task里面装的是一个可调用对象,
如果不调用它,是不会有结果返回来的。
std::package_task 不会自己启动,必须我们自己去调用它。
方式一
// 调用方式1,使用thead启动
#include <iostream>
#include <future>
#include <chrono>
#include <functional>
int Test_Fun(int a, int b, int &c)
{
//a=1,b=2,c=0
//突出效果,休眠5s
std::this_thread::sleep_for(std::chrono::seconds(5));
//c=233
c = a + b + 230;
return c;
}
int main()
{
//声明一个std::packaged_task对象pt1,包装函数Test_Fun
std::packaged_task<int(int, int, int&)> pt1(Test_Fun);
//声明一个std::future对象fu1,包装Test_Fun的返回结果类型,并与pt1关联
// 注意这里的future里面只有一个int,因为future只包含返回值
std::future<int> fu1 = pt1.get_future();
//声明一个变量
int c = 0;
//创建一个线程t1,将pt1及对应的参数放到线程里面执行
// 通过 thread 调用函数
std::thread t1(std::move(pt1), 1, 2, std::ref(c));
//阻塞至线程t1结束(函数Test_Fun返回结果)
int iResult = fu1.get();
std::cout << "执行结果:" << iResult << std::endl; //执行结果:233
std::cout << "c:" << c << std::endl; //c:233
return 1;
}
方式二
// 调用方式二,使用仿函数启动
#include <iostream> // std::cout
#include <thread> // std::thread
#include <chrono>
#include <future>
using namespace std;
//普通函数
int Add(int x, int y)
{
return x + y;
}
void task_lambda()
{
//包装可调用目标时lambda
packaged_task<int(int,int)> task([](int a, int b){ return a + b;});
//仿函数形式,启动任务
task(2, 10);
//获取共享状态中的值,直到ready才能返回结果或者异常
future<int> result = task.get_future();
cout << "task_lambda :" << result.get() << "\n";
}
void task_thread()
{
//包装普通函数
std::packaged_task<int (int,int)> task(Add);
future<int> result = task.get_future();
//启动任务,非异步
task(4,8);
cout << "task_thread :" << result.get() << "\n";
//重置共享状态
task.reset();
result = task.get_future();
//通过线程启动任务,异步启动
thread td(move(task), 2, 10);
td.join();
//获取执行结果
cout << "task_thread :" << result.get() << "\n";
}
int main(int argc, char *argv[])
{
task_lambda();
task_thread();
return 0;
}
//函数
int Test_Fun(int iVal)
{
std::cout << "Value is:" << iVal << std::endl;
return iVal + 232;
}
//声明一个std::packaged_task对象pt1,包装函数Test_Fun
std::packaged_task<int(int)> pt1(Test_Fun);
//声明一个std::future对象,包装Test_Fun的返回结果,并与pt1关联
std::future<int> fu1 = pt1.get_future();
// 这里添加一种启动方式
// pt1(1);
// std::thread t1(std::move(pt1),1); t1.join();
int result = fu1.get()
注意:使用std::packaged_task关联的std::future对象保存的数据类型是可调对象的返回结果类型,
如示例函数的返回结果类型是int,那么声明为std::future< int >,而不是std::future< int (int) >
状态延迟
在promise中,如果想在线程结束后,才把共享状态置为ready,
使用的是set_value_at_thread_exit/set_exception_at_thread_exit
在packaged_task中使用的是make_ready_at_thread_exit
// Not supported by g++ 4.9.3 or Visual Studio 2013
#include <future>
#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <utility>
void worker(std::future<void>& output)
{
std::packaged_task<void(bool&)> my_task{ [](bool& done) { done=true; } };
auto result = my_task.get_future();
bool done = false;
my_task.make_ready_at_thread_exit(done); // execute task right away
std::cout << "worker: done = " << std::boolalpha << done << std::endl;
auto status = result.wait_for(std::chrono::seconds(0));
if (status == std::future_status::timeout)
std::cout << "worker: result is not ready yet" << std::endl;
output = std::move(result);
}
int main()
{
std::future<void> result;
std::thread{worker, std::ref(result)}.join();
auto status = result.wait_for(std::chrono::seconds(0));
if (status == std::future_status::ready)
std::cout << "main: result is ready" << std::endl;
}
重置状态
promise 在使用完一次后(即成功取值后),就无法继续使用了
而packaged_task,在使用过一次后,可以通过reset函数重获新生
#include <iostream>
#include <cmath>
#include <thread>
#include <future>
int main()
{
std::packaged_task<int(int,int)> task([](int a, int b) {
return std::pow(a, b);
});
std::future<int> result = task.get_future();
task(2, 9);
std::cout << "2^9 = " << result.get() << '\n';
task.reset();
result = task.get_future();
std::thread task_td(std::move(task), 2, 10);
task_td.join();
std::cout << "2^10 = " << result.get() << '\n';
}
/* Output:
2^9 = 512
2^10 = 1024
*/
参考