第九节、async、future、packaged_task、promise
一、std::async、std::future创建后台任务并返回值
- std::async是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个std::future对象,这个对象是个类模板。
- 什么叫“启动一个异步任务”?就是自动创建一个线程,并开始执行对应的线程入口函数,它返回一个std::future对象,这个std::future对象中就含有线程入口函数所返回的结果,我们可以通过调用future对象的成员函数get()来获取结果。(以前是把线程入口函数的返回结果赋给一个全局量,或者传个引用、指针之类的也可以,现在std::future提供了一个新方法,把线程的返回值带回来)
- “future” 将来的意思,也有人称呼std::future提供了一种访问异步操作结果的机制,就是说这个结果你可能没办法马上拿到,但是在不久的将来,这个线程执行完毕的时候,你就能够拿到结果了,所以,大家这么理解:future对象中保存着一个值,这个值是在将来的某个时刻能够拿到。
- std::future对象的get()成员函数会等待线程执行结束并返回结果,拿不到结果它就会一直等待,感觉有点像join(),但是get()是可以获取结果的。
- std::future对象的wait()成员函数,用于等待线程返回,本身并不返回结果,这个效果和 std::thread 的join()更像。
- get()只能调用一次,多次调用会报异常。
#include <iostream>
#include <future>
#include <thread>
using namespace std;
class A
{
public:
int mythread(int mypar)//写成成员函数
{
cout << "mypar" << endl;
cout << "mythread() start" << " thread id = " << std::this_thread::get_id() << endl;//打印新线程id
std::chrono::milliseconds dura(5000);//定义5秒的时间
std::this_thread::sleep_for(dura);//休息5秒
cout << "mythread() end" << " thread id = " << std::this_thread::get_id() << endl;//打印新线程id
return 5;//线程返回值
}
};
int mythread(int mypar)
{
cout << "mypar" << endl;
cout << "mythread() start" << " thread id = " << std::this_thread::get_id() << endl;//打印新线程id
std::chrono::milliseconds dura(5000);//定义5秒的时间
std::this_thread::sleep_for(dura);//休息5秒
cout << "mythread() end" << " thread id = " << std::this_thread::get_id() << endl;//打印新线程id
return 5;//线程返回值
}
int main() {
A a;
int tmp = 12;
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;//打印主线程id
std::future<int> result1 = std::async(mythread, tmp);//int是将来返回值的类型,fuyure和线程绑定在一起了,流程并不卡到这里
cout << "continue........" << endl;
cout << result1.get() << endl; //卡在这里等待mythread()执行完毕,拿到结果后主线程才往下走
//result1.wait();
cout << "i love china!" << endl;
//用类成员函数作为线程入口函数
std::future<int> result2 = std::async(&A::mythread, &a, tmp); //第二个参数是对象引用才能保证线程里执行的是同一个对象,第三个参数才是传给线程入口函数的参数
cout << result2.get() << endl;//最好是get下,不然不太稳定
//或者result2.wait();
cout << "good luck" << endl;
return 0;
}
-
我们通过向std::async()传递一个参数,该参数是std::launch类型(枚举类型),来达到一些特殊的目的:
- 1.std::lunch::deferred:
(defer推迟,延期)表示线程入口函数的调用会被延迟,一直到std::future的wait()或者get()函数被调用时(由主线程调用)才会执行;如果wait()或者get()没有被调用,则不会执行,实际上根本就没有创建新线程,async那句等于白写。 - std::lunch::deferred的意思时延迟调用,并没有创建新线程,是在主线程中调用的线程入口函数(通过下面截图线程id可看出)。
- 1.std::lunch::deferred:
#include <iostream>
#include <future>
using namespace std;
int mythread() {
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(std::launch::deferred ,mythread);
cout << "continue........" << endl;
cout << result1.get() << endl;
cout << "good luck" << endl;
return 0;
}
-
永远都会先打印出continue…,然后才会打印出mythread() start和mythread() end等信息,说明是在调用get或者wait时才执行线程入口函数的。
-
2.std::launch::async,在调用async函数的时候就开始创建新线程,并立即执行。
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::future<int> result1 = std::async(std::launch::async ,mythread);
cout << "continue........" << endl;
cout << result1.get() << endl;
cout << "good luck" << endl;
return 0;
}
- 两个标记联合使用std::launch::async | std::launch::deferred在下一节深入详解会讲到
二、std::packaged_task:打包任务,把任务包装起来。
- 类模板,它的模板参数是各种可调用对象,通过packaged_task把各种可调用对象包装起来,方便将来作为线程入口函数来调用。
#include <iostream>
#include <thread>
#include <future>
using namespace std;
int mythread(int mypar) {//线程入口函数
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main" << " thread id = " << std::this_thread::get_id() << endl;
//我们把函数mythread通过packaged_task包装起来
std::packaged_task<int(int)> mypt(mythread);//第一个int是返回类型,第二个int是参数类型,mypt是类模板对象名,mythread是可调用对象(线程入口函数名)
std::thread t1(std::ref(mypt), 1);//std::ref纯引用,防止复制,第一个参数用经过包装的线程入口函数,第二个参数1是给线程入口函数的参数
t1.join();
//用future和packaged_task捆绑在一块
std::future<int> result = mypt.get_future();
//std::future对象里包含有线程入口函数的返回结果,这里result对象保存mythread返回的结果。
cout << result.get() << endl;//这里不会卡,因为上面join保证子线程肯定执行完了
return 0;
}
- 可调用对象可由函数换成lambda表达式
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
//包装一个lambda表达式,lambda表达式就是线程入口函数
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
});
std::thread t1(std::ref(mypt), 1);
t1.join();
std::future<int> result = mypt.get_future();
//std::future对象里包含有线程入口函数的返回结果,这里result保存mythread返回的结果。
cout << result.get() << endl;
cout << "good luck" << endl;
return 0;
}
- packaged_task包装起来的可调用对象还可以直接调用,从这个角度来讲,packaged_task对象也是一个可调用对象
- lambda的直接调用:
int main() {
cout << "main" << "threadid = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
});
mypt(1);//直接调用,相当于函数调用,没创建线程
std::future<int> result = mypt.get_future();
cout << result.get() << endl;
return 0;
}
- std::packaged_task的其他用法,放在容器里
#include <thread>
#include <iostream>
#include <future>
using namespace std;
std::vector<std::packaged_task<int(int)>> mytask;//容器
int main() {
//lambda表达式
std::packaged_task<int(int)> mypt([](int mypar) {
cout << mypar << endl;
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
return 5;
});
mytask.push_back(std::move(mypt));//用了移动语义,拷贝会报语法错,入容器后,mypt就为空了
//... 调试中显示的pending是挂起之意
std::packaged_task<int(int)> mypt2;
auto iter = mytask.begin();//迭代器
mypt2 = std::move(*iter);//移动语义,把mypt1移动到mypt2
mytask.erase(iter);//把容器中那一项删除,删除第一个元素,迭代器已经失效,后续代码不可以再使用iter
mypt2(123);
std::future<int> result = mypt2.get_future();
cout << result.get() << endl;
return 0;
}
- std::promise,也是个类模板,我们能够在某个线程中给它赋值,然后我们可以在其他线程中,把这个值取出来用
#include <iostream>
#include <thread>
#include <future>
using namespace std;
void mythread(std::promise<int> &tmp, int clac)
{
//做一系列复杂的操作
clac++;
clac *= 10;
//做其他操作花费5秒
cout << "mythread() start" << "threadid = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout << "mythread() end" << "threadid = " << std::this_thread::get_id() << endl;
//终于算出结果了
int result = clac;//保存结果
tmp.set_value(result); //结果保存到了tmp这个对象中
return;
}
void mythread2(std::future<int> &tmpf)
{
auto result = tmpf.get();
cout << "mythread2 result:" << result << endl;
return;
}
int main()
{
std::promise<int> myprom;//声明一个promise对象,保存值的类型是int
std::thread t1(mythread, std::ref(myprom), 180);//第二三个参数是线程入口函数的参数
t1.join(); //在这里线程已经执行完了
//主线程中获取结果值
std::future<int> fu1 = myprom.get_future(); //promise和future绑定,用于获取线程返回值
//auto result = fu1.get();
//cout << "result = " << result << endl;
std::thread t2(mythread2, std::ref(fu1));//把fu1拿到的线程1的值传到线程2中打印出来,实现两个子线程间的数值传递,线程2能拿到线程1中的工作成果
t2.join();
return 0;
}
总结:通过promise保存一个值,在将来某个时刻我们通过把一个future绑定到这个promise上,来得到绑定的值
- 注意:使用thread时,必须用 join() 等它,否则程序会报异常
小结:学这些到底怎么用,什么时候用
- 我们学习这些东西的目的并不是要把他们都用到实际开发中,不是为了炫技。
- 相反,如果我们能够用最少的东西写出一个稳定的,高效的多线程程序,更值得赞赏。
- 我们为了成长必须阅读一些高手写的代码,从而实现自己代码的积累。
- 学习这些东西的理由:为了我们将来我们能读懂高手写的代码铺路。