cpp多线程之std::future

C++多线程编程之std::future

1. std::future

std::future 通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值。如果共享状态的标志不为 还没有被 Provider 设置,则调用 std::future::get() 会阻塞当前的调用者,直到 Provider 设置了共享状态的值,std::future::get() 返回异步任务的值异常(如果发生了异常)。一句话概括,std::future 是为了线程间传递数据使用的。
一共有三种 Provider:

  1. std::async
  2. std::promise 调用 get_future() 函数
  3. std::packaged_task 调用 get_future() 函数

提示,std::future 独占资源,类似 unique_ptr,不可被拷贝。接下来看下三种 Provider 的用法。

2. std::async

先看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int main() {
std::future<int> fu = std::async([](int a, int b) {
std::cout << "thread start!" << std::endl;
return a + b;
}, 10, 20);

std::this_thread::sleep_for(std::chrono::milliseconds(2000));
int res = fu.get();
std::cout << "res=" << res << std::endl;
}

编译的时候需要链接pthread库。
运行结果:

1
2
thread start!
res=30

运行后立刻出现"thread start!"的字样,过了两秒出现结果。这是因为在运行 std::async 那一行的时候,直接会新建一个线程,执行函数的工作。这里使用的是 lambda 表达式,如果要传入自定义的函数需要加上 std::ref(函数名),传入函数引用(因为线程传入的是拷贝)。最后的 10 和 20 是运行所需的参数。之后主线程(主进程)睡眠 2000ms,阻塞在 fu.get() 上,直到线程运行完毕,最后打印结果。
综上所述,async 是为了把函数返回值带出来,用future来获取的。
这里注意有一个坑,如果不用 fu 来接收 std::async 的话,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int main() {
auto fu = std::async([](int a, int b) {
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "thread start!" << std::endl;
return a + b;
}, 10, 20);

std::cout << "main" << std::endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int main() {
std::async([](int a, int b) {
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
std::cout << "thread start!" << std::endl;
return a + b;
}, 10, 20);

std::cout << "main" << std::endl;
}

这两段代码运行结果有什么不同呢?
第一个用 fu 来接收了 std::future,导致这个 future 对象还没有被析构。所以是先打印 main,后面再过两秒打印出 thread start,之后 future 对象才因为程序结束离开作用域被析构。
第二个代码因为没有接收 future 对象,导致这个右值直接析构。所以主进程等待 future 析构完成再继续往下走,所以打印是先等待两秒,之后打印 thread start,紧接着打印 main

3. std::promise

直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int main() {
std::promise<int> pr;
auto fu = pr.get_future();
std::thread t1([](std::promise<int>& p, int a){
std::cout << "thread start!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
p.set_value(a);
std::cout << a << std::endl;
}, std::ref(pr), 2);
std::cout << "main" << std::endl;
std::cout << "fu.get()=" << fu.get() << std::endl;
t1.join();
}

返回结果:

1
2
3
4
main
fu.get()=thread start!
2
2

首先promise返回了一个 future 对象,表示 promise 承诺要传一个数给 future。之后运行线程 t1,给他传入了 promise 和一个数。在线程中,给 promise 设置了一个值。此时外面的 future 就可以调用 get() 方法获得线程中设置的 a。至于打印为何会断开,是因为在调用 fu.get() 的时候阻塞在那里了,所以先把前半段打印出来,等待 promise 传值给 future。
综上所述,promise 是用来和 future 绑定,给他传数据的。

3. std::packaged_task

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include <thread>
#include <future>
#include <chrono>

int main() {
std::packaged_task<int(int, int)> ta([](int a, int b) {
std::cout << "thread start!" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
return a + b;
});
auto fu = ta.get_future();
std::cout << "start to create thread" << std::endl;
std::thread t1(std::ref(ta), 2, 3);
std::cout << "fu.get()=" << fu.get() << std::endl;
t1.join();
}
1
2
3
start to create thread
fu.get()=thread start!
5

packaged_task 和一个普通的仿函数没有区别,只是返回值可以通过 future 来接收。结果同上,会在调用 fu.get() 的时候阻塞,等待线程 t1 执行完毕。
综上所述,packaged_task 是用来将一个 future 和一个函数绑定,返回函数执行结果的。和 async 的区别是它可以延迟启动,由 thread 来调用。当然你想要使用

1
2
ta(2, 3);
std::cout << fu.get();

那也可以,毕竟他也是个仿函数。