future

std::future

std::promise类

promise 对象可以保存某一类型 T 的值,该值可被 future 对象读取(可能在另外一个线程中),因此 promise 也提供了一种线程同步的手段。在 promise 对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为 T 的值。

可以通过 get_future 来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)

promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。
future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>       // std::cout
#include <functional> // std::ref
#include <thread> // std::thread
#include <future> // std::promise, std::future

void print_int(std::future<int>& fut) {
int x = fut.get(); // 获取共享状态的值.
std::cout << "value: " << x << '\n'; // 打印 value: 10.
}

int main ()
{
std::promise<int> prom; // 生成一个 std::promise<int> 对象.
std::future<int> fut = prom.get_future(); // 和 future 关联.
std::thread t(print_int, std::ref(fut)); // 将 future 交给另外一个线程t.
prom.set_value(10); // 设置共享状态的值, 此处和线程t保持同步.
t.join();
return 0;
}

std::promise构造函数

默认构造函数,初始化一个空的共享状态。
带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。
拷贝构造函数,被禁用。
移动构造函数。

另外,std::promise 的 operator= 没有拷贝语义,即 std::promise 普通的赋值操作被禁用,operator= 只有 move 语义,所以 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
#include <iostream>       // std::cout
#include <thread> // std::thread
#include <future> // std::promise, std::future

std::promise<int> prom;

void print_global_promise () {
std::future<int> fut = prom.get_future();
int x = fut.get();
std::cout << "value: " << x << '\n';
}

int main ()
{
std::thread th1(print_global_promise);
prom.set_value(10);
th1.join();

prom = std::promise<int>(); // prom 被move赋值为一个新的 promise 对象.

std::thread th2 (print_global_promise);
prom.set_value (20);
th2.join();

return 0;
}

std::packaged_task类

std::packaged_task 包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果,从包装可调用对象意义上来讲,std::packaged_task 与 std::function 类似,只不过 std::packaged_task 将其包装的可调用对象的执行结果传递给一个 std::future 对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。

std::packaged_task 对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future 对象来达到异步访问共享状态的效果。

可以通过 std::packged_task::get_future 来获取与共享状态相关联的 std::future 对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:

std::packaged_task 对象是异步 Provider,它在某一时刻通过调用被包装的任务来设置共享状态的值。
std::future 对象是一个异步返回对象,通过它可以获得共享状态的值,当然在必要的时候需要等待共享状态标志变为 ready.
std::packaged_task 的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。

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
#include <iostream>     // std::cout
#include <future> // std::packaged_task, std::future
#include <chrono> // std::chrono::seconds
#include <thread> // std::thread, std::this_thread::sleep_for

// count down taking a second for each value:
int countdown (int from, int to) {
for (int i=from; i!=to; --i) {
std::cout << i << '\n';
std::this_thread::sleep_for(std::chrono::seconds(1));
}
std::cout << "Finished!\n";
return from - to;
}

int main ()
{
std::packaged_task<int(int,int)> task(countdown); // 设置 packaged_task
std::future<int> ret = task.get_future(); // 获得与 packaged_task 共享状态相关联的 future 对象.

std::thread th(std::move(task), 10, 0); //创建一个新线程完成计数任务.

int value = ret.get(); // 等待任务完成并获取结果.

std::cout << "The countdown lasted for " << value << " seconds.\n";

th.join();
return 0;
}

std::packaged_task构造函数

std::packaged_task 构造函数共有 5 中形式,不过拷贝构造已经被禁用了。下面简单地介绍一下几种构造函数的语义:

默认构造函数,初始化一个空的共享状态,并且该 packaged_task 对象无包装任务。
初始化一个共享状态,并且被包装任务由参数 fn 指定。
带自定义内存分配器的构造函数,与默认构造函数类似,但是使用自定义分配器来分配共享状态。
拷贝构造函数,被禁用。
移动构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>     // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread

int main ()
{
std::packaged_task<int(int)> foo; // 默认构造函数.

// 使用 lambda 表达式初始化一个 packaged_task 对象.
std::packaged_task<int(int)> bar([](int x){return x*2;});

foo = std::move(bar); // move-赋值操作,也是 C++11 中的新特性.

// 获取与 packaged_task 共享状态相关联的 future 对象.
std::future<int> ret = foo.get_future();

std::thread(std::move(foo), 10).detach(); // 产生线程,调用被包装的任务.

int value = ret.get(); // 等待任务完成并获取结果.
std::cout << "The double of 10 is " << value << ".\n";

return 0;
}

std::packaged_task::valid

检查当前 packaged_task 是否和一个有效的共享状态相关联,对于由默认构造函数生成的 packaged_task 对象,该函数返回 false,除非中间进行了 move 赋值操作或者 swap 操作

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
#include <iostream>     // std::cout
#include <utility> // std::move
#include <future> // std::packaged_task, std::future
#include <thread> // std::thread

// 在新线程中启动一个 int(int) packaged_task.
std::future<int> launcher(std::packaged_task<int(int)>& tsk, int arg)
{
if (tsk.valid()) {
std::future<int> ret = tsk.get_future();
std::thread (std::move(tsk),arg).detach();
return ret;
}
else return std::future<int>();
}

int main ()
{
std::packaged_task<int(int)> tsk([](int x){return x*2;});

std::future<int> fut = launcher(tsk,25);

std::cout << "The double of 25 is " << fut.get() << ".\n";

return 0;
}

std::packaged_task::reset

重置 packaged_task 的共享状态,但是保留之前的被包装的任务

std::packaged_task::swap

交换 packaged_task 的共享状态

std::future 类

std::future 究竟是什么呢?简单地说,std::future 可以用来获取异步任务的结果,因此可以把它当成一种简单的线程间同步的手段。std::future 通常由某个 Provider 创建,你可以把 Provider 想象成一个异步任务的提供者,Provider 在某个线程中设置共享状态的值,与该共享状态相关联的 std::future 对象调用 get(通常在另外一个线程中) 获取该值,如果共享状态的标志不为 ready,则调用 std::future::get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get 返回异步任务的值或异常(如果发生了异常)。

一个有效(valid)的 std::future 对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,其实我们前面都已经提到了,他们分别是:

std::async 函数,本文后面会介绍 std::async() 函数。
std::promise::get_future,get_future 为 promise 类的成员函数
std::packaged_task::get_future, get_future为 packaged_task 的成员函数
一个 std::future 对象只有在有效(valid)的情况下才有用(useful),由 std::future 默认构造函数创建的 future 对象不是有效的(除非当前非有效的 future 对象被 move 赋值另一个有效的 future 对象)。

在一个有效的 future 对象上调用 get 会阻塞当前的调用者,直到 Provider 设置了共享状态的值或异常(此时共享状态的标志变为 ready),std::future::get 将返回异步任务的值或异常(如果发生了异常)。

std::async

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
// future example
#include <iostream> // std::cout
#include <future> // std::async, std::future
#include <chrono> // std::chrono::milliseconds

// a non-optimized way of checking for prime numbers:
bool
is_prime(int x)
{
for (int i = 2; i < x; ++i)
if (x % i == 0)
return false;
return true;
}

int
main()
{
// call function asynchronously:
std::future < bool > fut = std::async(is_prime, 444444443);

// do something while waiting for function to set future:
std::cout << "checking, please wait";
std::chrono::milliseconds span(100);
while (fut.wait_for(span) == std::future_status::timeout)
std::cout << '.';

bool x = fut.get(); // retrieve return value

std::cout << "\n444444443 " << (x ? "is" : "is not") << " prime.\n";

return 0;
}

std::future构造函数

std::future 的拷贝构造函数是被禁用的,只提供了默认的构造函数和 move 构造函数。另外,std::future 的普通赋值操作也被禁用,只提供了 move 赋值操作

std::future::share

返回一个 std::shared_future 对象,调用该函数之后,该 std::future 对象本身已经不和任何共享状态相关联,因此该 std::future 的状态不再是 valid 的了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>       // std::cout
#include <future> // std::async, std::future, std::shared_future

int do_get_value() { return 10; }

int main ()
{
std::future<int> fut = std::async(do_get_value);
std::shared_future<int> shared_fut = fut.share();

// 共享的 future 对象可以被多次访问.
std::cout << "value: " << shared_fut.get() << '\n';
std::cout << "its double: " << shared_fut.get()*2 << '\n';

return 0;
}

std::future::get

当与该 std::future 对象相关联的共享状态标志变为 ready 后,调用该函数将返回保存在共享状态中的值,如果共享状态的标志不为 ready,则调用该函数会阻塞当前的调用者,而此后一旦共享状态的标志变为 ready,get 返回 Provider 所设置的共享状态的值或者异常(如果抛出了异常)

std::future::valid

检查当前的 std::future 对象是否有效,即释放与某个共享状态相关联。一个有效的 std::future 对象只能通过 std::async(), std::future::get_future 或者 std::packaged_task::get_future 来初始化。

std::future::wait

等待与当前std::future 对象相关联的共享状态的标志变为 ready.

如果共享状态的标志不是 ready(此时 Provider 没有在共享状态上设置值(或者异常)),调用该函数会被阻塞当前线程,直到共享状态的标志变为 ready。
一旦共享状态的标志变为 ready,wait() 函数返回,当前线程被解除阻塞,但是 wait() 并不读取共享状态的值或者异常。

std::future::wait_for

与 std::future::wait() 的功能类似,即等待与该 std::future 对象相关联的共享状态的标志变为 ready,而与 std::future::wait() 不同的是,wait_for() 可以设置一个时间段 rel_time,如果共享状态的标志在该时间段结束之前没有被 Provider 设置为 ready,则调用 wait_for 的线程被阻塞,在等待了 rel_time 的时间长度后 wait_for() 返回(future_status::ready、future_status::timeout、future_status::deferred)

std::future::wait_until

与std::future::wait_for类似

std::shared_future类

std::shared_future 与 std::future 类似,但是 std::shared_future 可以拷贝、多个 std::shared_future 可以共享某个共享状态的最终结果(即共享状态的某个值或者异常)。shared_future 可以通过某个 std::future 对象隐式转换(参见 std::shared_future 的构造函数),或者通过 std::future::share() 显示转换,无论哪种转换,被转换的那个 std::future 对象都会变为 not-valid.

std::future_error类

std::future_error 继承子 C++ 标准异常体系中的 logic_error

std::future枚举类

enum class future_errc;
enum class future_status;
enum class launch;
下面分别介绍以上三种枚举类型:

std::future_errc 类型描述如下(参考):
类型 取值 描述
broken_promise 0 与该 std::future 共享状态相关联的 std::promise 对象在设置值或者异常之前一被销毁。
future_already_retrieved 1 与该 std::future 对象相关联的共享状态的值已经被当前 Provider 获取了,即调用了 std::future::get 函数。
promise_already_satisfied 2 std::promise 对象已经对共享状态设置了某一值或者异常。
no_state 3 无共享状态。

std::future_status 类型主要用在 std::future(或std::shared_future)中的 wait_for 和 wait_until 两个函数中的。
类型 取值 描述
future_status::ready 0 wait_for(或wait_until) 因为共享状态的标志变为 ready 而返回。
future_status::timeout 1 超时,即 wait_for(或wait_until) 因为在指定的时间段(或时刻)内共享状态的标志依然没有变为 ready 而返回。
future_status::deferred 2 共享状态包含了 deferred 函数。

std::launch 类型,该枚举类型主要是在调用 std::async 设置异步任务的启动策略的。

类型 描述
launch::async Asynchronous: 异步任务会在另外一个线程中调用,并通过共享状态返回异步任务的结果(一般是调用 std::future::get() 获取异步任务的结果)。
launch::deferred Deferred: 异步任务将会在共享状态被访问时调用,相当与按需调用(即延迟(deferred)调用)。