获取线程函数返回值

九、获取线程函数返回值

本节主要记录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); // 预定义一个5s的时间
std::this_thread::sleep_for(dura); // 休息一定时长
cout << "mythread() end, " << "threadid = " << this_thread::get_id() << endl;
return 10;
}

int main()
{
// 下列程序通过std::future对象的get()成员函数等待线程执行结束并返回结果
// get()拿不到将来的返回值,就卡住直到拿到值
cout << "main. " << "threadid = " << this_thread::get_id() << endl;
std::future<int> result = std::async(mythread); // 创建一个线程并开始执行,绑定关系
cout << "continue...!" << endl;
// result.wait(); // 等待线程返回,本身不返回结果
cout << result.get() << endl; // 卡在这里等待mythread()线程执行完
// get()只能调用一次
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::ref(A),不然会重新穿件类A

我们可以通过额外向std::async传递一个参数(第一个形参),该参数类型是std::lunch类型(枚举类型),来达到一些特殊的目的;

  1. std::launch::deferred:表示线程入口函数调用被延迟到std::futurewait()或者get()函数调用才执行。如果wait()get()没有调用,则不会创建线程并执行线程入口函数;如果在主线程中调用wait()get(),实际上也不会创建新线程,而是在主线程中执行的入口函数。
  2. 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(std::launch::deferred,mythread, 100);
std::future<int> result = std::async(mythread, 100); // 这个异步线程会执行5秒左右
std::future_status status = result.wait_for(std::chrono::seconds(1)); // 等待1秒钟
if (status == std::future_status::timeout)
{
cout << "超时,线程还没执行完!" << endl;
}
else if (status == std::future_status::ready) // 若等待6s,会是这个状态
{
cout << "线程成功执行完毕,并返回" << endl;
}
else if (status == std::future_status::deferred)
{
// 如果async的第一个参数被设置为std::launch::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); // 把函数mythread通过packaged_task包装起来
std::thread t1(std::ref(mypt), 10); // 线程直接开始执行,第二个参数作为线程入口函数的参数
t1.join(); // 等待线程执行完毕
std::future<int> result = mypt.get_future(); // result保存线程入口函数返回的结果
// std::shared_future<int> result_s = mypt.get_future();
cout << result.get() << endl; // 由于join()的关系,这里不会阻塞了
return 0;
}

packaged_task包装起来的可调用对象也可以直接调用,从这个角度来讲,packaged_task对象,也是一个可调用对象。

1
2
3
mypt(10);                                      // 直接调用,相当于函数调用不会创建线程
std::future<int> result = mypt.get_future(); // 通过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); // 预定义一个5s的时间
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) // 在这个线程入口函数中给promise赋值
{
cout << "mythread1() start, " << "threadid = " << this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000); // 预定义一个5s的时间
std::this_thread::sleep_for(dura); // 休息一定时长,模拟一些费时的操作
tmpp.set_value(val); // 结果保存到了tmpp对象中
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::promis对象,保存的值类型为int;
std::thread t1(mythread1, std::ref(myprom), 100); // 这个线程开始执行,(这里使用引用)

std::future<int> thread1_result = myprom.get_future(); // promise和future绑定用于获取线程返回值
std::thread t2(mythread2, std::ref(thread1_result));
t1.join(); // 用了thread就该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(); // 获取值,为什么只能get一次,否则出来异常?
// 主要是因为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); // 预定义一个5s的时间
std::this_thread::sleep_for(dura); // 休息一定时长
tmpp.set_value(val); // 结果保存到了tmpp对象中
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(); // 获取值,为什么只能get一次,否则报出来异常?
// 主要是因为get()函数的设计,它是一个移动语义。
cout << "mythread2 result" << result << endl;
return;
}

int main()
{
cout << "main. " << "threadid = " << this_thread::get_id() << endl;
std::promise<int> myprom; // 声明一个std::promis对象,保存的值类型为int;
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(); // promise和future绑定用于获取线程返回值
//std::shared_future<int> thread1_result_shared(std::move(thread1_result)); // 必须转换为右值,这句执行完毕后,thread1_result_shared里有值;
std::shared_future<int> thread1_result_shared(thread1_result.share()); // 等同于上一句 thread1_result为空
bool ifcanget = thread1_result.valid(); // 判断future对象是否有有效值
std::thread t2(mythread2, std::ref(thread1_result_shared));
t1.join(); // 用了thread就该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::future<int> result = mypt.get_future();    // result保存线程入口函数返回的结果
std::shared_future<int> result_s = mypt.get_future();

但是std::promise::get_future()不可以与std::shared_future直接绑定。

听说打赏我的人,最后都找到了真爱。