九、获取线程函数返回值
本节主要记录std::async
函数模板、std::future
类模板、std::packaged_task
类模板、std::promise
类模板的使用。
1、std::async、std::future创建后台任务并返回值
有的时候我们需要线程返回结果,一个实现手段是将结果赋给全局变量,但是这里我们有一个更好的处理方式。std::async
是一个函数模板,用来启动一个异步任务,启动起来一个异步任务之后,它返回一个类模板std::future
的对象。(何为启动一个异步任务:自动创建一个线程并开始执行对应的线程入口函数。)这个std::future
对象里边就含有线程入口函数所返回的结果,我们可以通过调用std::future
的成员函数get()
来获取结果。
std::future
提供了一种访问异步操作结果的机制,就是说这个结果可能无法马上获取,但是在线程执行完毕的时候,就可以获取结果了。可以这样理解future
对象:它里边保存一个值,在将来的某个时刻能够拿到。下面的例子来演示std::future
的一些函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include "pch.h" #include <iostream> #include <thread> #include <future> #include <chrono> using namespace std; int mythread() { cout << "mythread() start, " << "threadid = " << this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); cout << "mythread() end, " << "threadid = " << this_thread::get_id() << endl; return 10; }
int main() { cout << "main. " << "threadid = " << this_thread::get_id() << endl; std::future<int> result = std::async(mythread); cout << "continue...!" << endl; cout << result.get() << endl; cout << "HELLO WORLD" << endl; return 0; }
|
以上程序会卡在result.get()
,等待mythread()
返回结果,如果不手动调用wait()
、get()
成员函数,主线程会在return 0
处等待子线程执行完成才能退出。
std::async
也可以类的成员函数为线程入口函数,使用方法和std::thread
类似。
1 2 3 4 5 6 7 8
| class A { public: int mythread(int val); };
int tmppar = 10; std::future<int> result = std::async(&A::mythread, &A, tmppar);
|
我们可以通过额外向std::async
传递一个参数(第一个形参),该参数类型是std::lunch
类型(枚举类型),来达到一些特殊的目的;
std::launch::deferred
:表示线程入口函数调用被延迟到std::future
的wait()
或者get()
函数调用才执行。如果wait()
和get()
没有调用,则不会创建线程并执行线程入口函数;如果在主线程中调用wait()
和get()
,实际上也不会创建新线程,而是在主线程中执行的入口函数。
std::launch::async
:表示强制这个异步任务在新线程上执行,在调用std::async
函数的时候就开始创建线程。std::async
默认的是这个标记。
2、std::future::wait_for()
wait_for()
成员函数模板:
1 2
| template <class Rep, class Period> future_status wait_for (const chrono::duration<Rep,Period>& rel_time) const;
|
它接收一段时间,返回的是一个枚举量,使用案例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| std::future<int> result = std::async(mythread, 100); std::future_status status = result.wait_for(std::chrono::seconds(1)); if (status == std::future_status::timeout) { cout << "超时,线程还没执行完!" << endl; } else if (status == std::future_status::ready) { cout << "线程成功执行完毕,并返回" << endl; } else if (status == std::future_status::deferred) { cout << "线程被延迟执行" << endl; cout << result.get() << endl; } cout << "result.get()= " << result.get() << endl;
|
3、std::packaged_task
打包任务,它是一个类模板,模板参数是各种可调用对象,通过std::packaged_task
来把各种可调用对象包装起来,方便将来作为线程入口函数。我们以上一个代码中的mythread()
函数作为线程入口函数,看std::packaged_task如何使用:
1 2 3 4 5 6 7 8 9 10 11
| int main() { cout << "main. " << "threadid = " << this_thread::get_id() << endl; std::packaged_task<int(int)> mypt(mythread); std::thread t1(std::ref(mypt), 10); t1.join(); std::future<int> result = mypt.get_future(); cout << result.get() << endl; return 0; }
|
packaged_task包装起来的可调用对象也可以直接调用,从这个角度来讲,packaged_task对象,也是一个可调用对象。
1 2 3
| mypt(10); std::future<int> result = mypt.get_future(); cout << result.get() << endl;
|
我们也可以对lambda表达式进行封装:
1 2 3 4 5 6 7 8
| std::packaged_task<int(int)> mypt2([](int mypar) { cout << "mythread() start, " << "threadid = " << this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); cout << "mythread() end, " << "threadid = " << this_thread::get_id() << endl; return mypar; });
|
也可以使用容器来存放packaged_task对象:
1 2 3 4 5 6
| vector<std::packaged_task<int(int)>> mytasks; mytasks.push_back(std::move(mypt)); std::packaged_task<int(int)> mypto; auto iter = mytasks.begin(); mypto = std::move(*iter); mytasks.erase(iter);
|
4、std::promise
std::promise
是一个类模板,我们能够在某个线程中给它赋值,然后在其他线程中把这个值取出来使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include "pch.h" #include <iostream> #include <thread> #include <future> #include <chrono>
using namespace std;
void mythread1(std::promise<int> &tmpp, int val) { cout << "mythread1() start, " << "threadid = " << this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); tmpp.set_value(val); cout << "mythread1() end, " << "threadid = " << this_thread::get_id() << endl; }
void mythread2(std::future<int>& tmpf) { cout << "mythread()2 start, " << "threadid = " << this_thread::get_id() << endl; auto result = tmpf.get(); cout << "mythread2 result" << result << endl; return; }
int main() { cout << "main. " << "threadid = " << this_thread::get_id() << endl; std::promise<int> myprom; std::thread t1(mythread1, std::ref(myprom), 100); std::future<int> thread1_result = myprom.get_future(); std::thread t2(mythread2, std::ref(thread1_result)); t1.join(); t2.join(); cout << "HELLO WORLD" << endl; return 0; }
|
总结:通过promise
保存一个值,在将来的某个时刻我们通过把一个future
绑定到这个promise上来得到线程中赋予的值。
5、std::shared_future
std::shared_future
是一个类模板。以上我们使用std::future
类模板的时候,只能调用一次get()
函数,若多次调用,系统会报异常:
1 2 3 4 5 6 7 8
| void mythread2(std::future<int>& tmpf) { cout << "mythread()2 start, " << "threadid = " << this_thread::get_id() << endl; auto result = tmpf.get(); cout << "mythread2 result" << result << endl; return; }
|
这主要是因为get()
函数使用的是移动语义,调用一次之后,类中的内容被移动走了,再次调用get()
函数便会出错。然而我们有时候需要在多个线程中获得future
对象的资源,这时候std::future::get()
就不适用了,而std::shared_future
便是解决这个问题的类模板。使用案例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include "pch.h" #include <iostream> #include <thread> #include <future> #include <chrono>
using namespace std;
void mythread1(std::promise<int> &tmpp, int val) { cout << "mythread() start, " << "threadid = " << this_thread::get_id() << endl; std::chrono::milliseconds dura(5000); std::this_thread::sleep_for(dura); tmpp.set_value(val); cout << "mythread() end, " << "threadid = " << this_thread::get_id() << endl; }
void mythread2(std::shared_future<int>& tmpf) { cout << "mythread()2 start, " << "threadid = " << this_thread::get_id() << endl; auto result = tmpf.get(); cout << "mythread2 result" << result << endl; return; }
int main() { cout << "main. " << "threadid = " << this_thread::get_id() << endl; std::promise<int> myprom; std::thread t1(mythread1, std::ref(myprom), 100); std::shared_future<int> result_s = myprom.get_future(); std::future<int> thread1_result = myprom.get_future(); std::shared_future<int> thread1_result_shared(thread1_result.share()); bool ifcanget = thread1_result.valid(); std::thread t2(mythread2, std::ref(thread1_result_shared)); t1.join(); t2.join(); cout << "HELLO WORLD" << endl; return 0; }
|
我们通过将std::future
对象移动给std::shared_future
,在其他线程中使用std::shared_future::get()
来获取值,这个get()
使用的是copy而不是move。
对于一些函数,我们可以直接与std::shared_future
绑定,而不是先与std::future
绑定,然后转移给std::shared_future
。例如std::async()
的返回结果可以直接与std::shared_future
绑定:
1 2
| std::shared_future<int> result_s = mypt.get_future();
|
但是std::promise::get_future()
不可以与std::shared_future
直接绑定。