对线程又有了深刻的认识,以前写的线程基本都是同步线程,而自从学习了muduo网络库更知道要用异步的思想去处理问题,因为有时候同步是必须的,但有的时候同步会造成本不必要的浪费,所以也适当的用异步操作来代替同步操作,故再次更新一下packaged_task和promise的使用方法。也了解了多线程重载函数要如何bind,packaged_task更新中有提到。
初学多线程的我天真的以为异步和多线程是一样的,直到我看见了std::async这个处理异步的函数(头文件在future里面),我才知道异步和多线程是不一样的。
关于多线程与异步区别生动形象的例子
为什么有了多线程还需要异步操作?身为初学的我想到了这个问题在网上百度了没百度到,好在今天读书的时候又找到了自己想要的答案了。首先要注意到开启一根多线程是不能保存返回值,那么聪明的你肯定也想到函数增加一个引用类型参数,不就解决了嘛,代码如下。这样确实解决问题,但我想真正的工程中,一般不会轻易修改一个函数的参数,一个是变得更难维护,另一个是如果使用的是dll或者lib文件还需要重新编译文件,这会相当的耗时(我还没遇到那么耗时的编译时间,纯属道听途说,平常都是写写自己的小玩具还是蛮期待以后工作中遇到的大工程)。综上所述那当我想要一个函数返回值的时候,我该怎么办才好?答案就是使用async这个函数,专门解决函数返回值带给你个烦恼,这函数老棒了
int future_fun(int &a) {
cout << "hello future_fun" << endl;
a = 1;
return 1;
}
int main() {
int a = 0;
thread t1(future_fun,std::ref(a));//多线程传递引用参数必须加
//std::ref表示传递引用型
//参数
t1.join();
cout << a << endl;
return 0;
}
async函数将返回值保存到future中去,当需要返回值的时候,用future的对象调用get()即可获得返回值。有两种方法可以实现,一种是延迟处理(还是一根线程,没有开启新的一根线程),一种是开启一根线程去处理。
默认的情况是开启一根新的线程,我们可以通过线程ID来判断是不是开了一根新的线程,如图所示
修正:看完effective modern C++之后又有了新的理解了,默认的情况不是想象中的那样。future有一个合理调度器,如果发生了超订现象, 系统不保证会在开一根线程的
延迟处理的情况就是这样定义的std::launch::deferred,前面加一个这个样的参数就代表是延迟处理了,如果是延迟处理的话,只有当f调用了wait()或者get()函数,函数才会被调用,否则延迟处理的async异步函数将永不会被处理。wait函数不返回结果,只是等待async结束,类似thread的join函数。那我怎么知道他延迟处理是一根线程的呢,我们可以查看他的线程id,线程id是唯一的,我们可知延迟处理并没有新增一根线程
future<int> f = std::async(std::launch::deferred,future_fun);
int future_fun() {
cout << "future_fun thread_id:" << std::this_thread::get_id() << endl;
return 1;
}
int main() {
future<int> f = std::async(std::launch::deferred,future_fun);
//future f = std::async( future_fun);
cout << "main thread_id:" << std::this_thread::get_id() << endl;
cout << f.get() << endl;
return 0;
}
很多函数都返回一个future对象,比如async、packaged_task、promise等函数都可以返回future对象。不过要注意的是future的get()函数只能调用一次,如果调用第二次会暴出错误的提示
该类模板调用get_future()可以返回一个future对象。下面让我们看看future的使用。还是很简单的。static函数最大的好处就是不会有命名冲突,暴露一个接口给main函数,剩下全定义成static函数,这样其他cpp文件不能调用当前cpp文件的函数。
直接用packaged_task对象调用
static int packaged_task_fun2() {
cout << "packaged_task_fun(int& val)" << endl;
return 2;
}
static void packaged_task_fun() {
const int& r = 0;
packaged_task<int()> pt1(packaged_task_fun2);
pt1();//可以直接调用
future<int> fu = pt1.get_future();
cout << fu.get() << endl;
}
void future_run() {
packaged_task_fun();
}
static int packaged_task_fun2(const int& r) {
cout << "packaged_task_fun2(int& val)" << endl;
return 2;
}
static void packaged_task_fun() {
//packaged_task 封装一个函数给thread然后再通过get_future函数获取future对象
int a = 2;
const int& r = a;
packaged_task<int(const int&)> pt1(packaged_task_fun2);
//packaged_task对象不支持拷贝,支持移动,在这里使用了引用
thread t1(std::ref(pt1),std::ref(r));
t1.join();
future<int> fu = pt1.get_future();
cout << fu.get() << endl;
}
/*
*如果不使用引用,那就要提前给fu赋值,不然std::move()之后,
*pt1就失效了
*/
void future_run() {
packaged_task_fun();
}
package_task是一个不错异步的函数接口,你可以利用占位符先来占用参数,然后等到合适的时机在执行此函数。在绑定重载函数的时候需要强制转换函数指针来确定绑定那个函数。
例如这样的,要调用带两个参数的fun,重载函数利用函数指针来进行绑定。当调用task的时候才开始执行函数,get返回结果,这样就是当你方便的时候在启动这个函数,比async更好的进行异步操作,更可控。
int fun(int ,int&) {
dxgzg::print("fun(int,int&)...");
return 22;
}
void fun() {
dxgzg::print("fun()...");
}
using pf_void = void(*)();
using pf_int = int(*)(int,int&);
inline void packaged_test() {
function<int(int,int&)> f = std::bind((pf_int)fun,std::placeholders::_1,std::placeholders::_2);
packaged_task<int(int,int&)> task(f);
future<int> ft = task.get_future();
int a = 2;
int& r = a;
task(a,r);// 不调用这个会一直阻塞的
cout << ft.get();
}
int main(){
packaged_test();
return 0;
}
promise类也是配合future用的。
promise对象调用get_future的时候,如果没有用set_value()设置值的话,这根线程会一直阻塞在这里,直到promise对象调用了set_value。set_value函数只能调用一次不可以多次调用!
void promise_fun1(promise<int>& p) {
p.set_value(2);
}
void promise_main() {
std::promise<int>p;
promise_fun1(p);
future<int> f = p.get_future();
cout << f.get() << endl;
}
void future_run() {
promise_main();
}
调用set_value才启动,换句话说,当future没得到值的线程就会一直阻塞,从而也保持了一定的同步,是set_value完future才不阻塞,所以promise是线程同步之间操作。
void promise_test(future<vector<int>> f) {
for(auto val : f.get()) {
dxgzg::print(val);
}
return;
}
int main() {
vector<int> vec{ 1,2,3 };
promise<vector<int>> p;
int a = 2;
int& r = a;
thread t(promise_test,p.get_future());
p.set_value(vec);
t.join();
return 0;
}