【C++11 多线程】future与promise(八)
一、C++11为什么要引入std::future?
我们经常会遇到需要从线程中返回异步任务结果的情况。例如在程序中,我们创建了一个压缩给定文件夹的线程,并且我们希望该线程能够返回新的 zip 文件的名称和大小。
在 C++11 之前的老方法是使用指针在线程间共享数据:
- 传递一个指针到新的线程中,该线程将在其中设置数据。直到主线程继续等待使用条件变量。当新线程设置数据并通知条件变量时,主线程将唤醒并从该指针处获取数据。
- 为了实现这一简单功能,我们使用了一个条件变量、一个 mutex 锁和一个指针,来实现捕获返回值。
#include<iostream>
#include<thread>
#include<mutex>
void fun(int x, int y, int* ans) {
*ans = x + y;
}
int main()
{
int a = 10;
int b = 8;
int* sum = new int(0);
std::thread t(fun, a, b, sum);
t.join();
// 获取线程的"返回值"
std::cout << *sum << std::endl; // 输出:18
delete sum;
system("pause");
return 0;
}
可以看到还需要使用外部变量,比较麻烦。如果我们想要该线程在不同的时间点返回 3 个不同的值,问题会变得更加复杂,有没有一种简单的方法来从线程处获取返回值呢?答案是 使用std::future
。
二、std::future介绍
C++11 提供了std::future
类模板,future 对象提供访问异步操作结果的机制,很轻松解决从异步任务中返回结果。
在 C++ 标准库中,有两种“期望”,使用两种类型模板实现(这里主要介绍的是唯一期望):
- 唯一期望(
unique futures
,std::future<>
) 实例只能与一个指定事件相关联。 - 共享期望(
shared futures
,std::shared_future<>
) 实例能关联多个事件。
事实上,一个std::future
对象在内部存储一个将来会被某个 provider 赋值的值,并提供了一个访问该值的机制,通过get()
成员函数实现。但如果有人试图在get()
函数可用之前通过它来访问相关的值,那么get()
函数将会阻塞,直到该值可用。
一个有效的std::future
对象通常由以下三种 Provider 创建,并和某个共享状态相关联。Provider 可以是函数或者类,他们分别是:
std::async
函数,本文后面会介绍std::promise::get_future
,get_future 为 promise 类的成员函数std::packaged_task::get_future
,此时 get_future为 packaged_task 的成员函数
C++11 提供的 <future> 头文件中包含了以下几个类和函数:
- Providers 类:std::promise, std::package_task
- Futures 类:std::future, shared_future.
- Providers 函数:std::async()
- 其他类型:std::future_error, std::future_errc, std::future_status, std::launch
三、std::future构造函数
std::future
一般由上面三种 Provider 创建,不过也提供了构造函数:
// default
future() noexcept;
// copy [deleted]
future (const future&) = delete;
// move
future (future&& x) noexcept;
不过std::future
的拷贝构造函数是被禁用的,只提供了默认的构造函数和 move 构造函数(注:C++ 新特性)。另外,std::future
的普通赋值操作也被禁用,只提供了 move 赋值操作。如下代码所示:
std::future<int> fut; // 默认构造函数
fut = std::async(do_some_task); // move-赋值操作。
四、std::future成员函数
其成员函数如下:
-
std::future::valid()
检查 future 对象是否拥有共享状态,参照构造函数只有两种可用,由默认构造函数创建的 future 对象显然不具有共享状态,即
valid()=false
,除非它被 move 赋值过;而移动构造函数创建的 future 对象往往拥有共享状态,只不过是否可以立即调用 get() 访问还需要确认共享状态标志是否已被设置为 ready。 -
std::future::get()
阻塞式获得共享状态的值,如果 future 对象调用 get() 时,共享状态标志尚未被设置为 ready,那么本线程将阻塞至其变为 ready。
-
std::future::wait()
等待共享状态标志变为 ready,在此之前线程将会一直阻塞。
-
std::future::wait_for()
与 wait() 不同,wait_for() 只会允许为此等待一段时间 _Rel_time,耗尽这个时间共享状态标志仍不为 ready,wait_for() 一样会返回。
-
std::future::wait_until()
与 wait_for() 类似的逻辑,只不过 wait_until() 参考的是绝对时间点。到达时间点 _Abs_time 的时候,wait_until() 就会返回,如果没等到 ready 的话,wait_until 一样会返回。
-
std::future::share()
返回一个 std::shred_future 对象,调用该函数之后,future 对象不和任何共享状态关联,也就不再是 valid 的了。
其中std::future::wait_for()
和std::future::wait_until()
的返回值如下:
- future_status::ready:共享状态的标志已经变为 ready,即 Provider 在共享状态上设置了值或者异常。
- future_status::timeout:超时,即在规定的时间内共享状态的标志没有变为 ready。
- future_status::deferred:共享状态包含一个 deferred 函数。
上面只是对各函数的简单介绍,具体示例请参考:C++11 并发指南四( 详解三 std::future & std::shared_future)
五、std::promise介绍
std::promise
的作用就是提供一个不同线程之间的数据同步机制,它可以存储一个某种类型的值,并将其传递给对应的 future, 即使这个 future 与 promise 不在同一个线程中也可以安全的访问到这个值。
可以通过get_future()
来获取与该 promise 对象相关联的 future 对象,调用该函数之后,两个对象共享相同的共享状态(shared state)。set_value()
可以设置共享状态的值,此后 promise 的共享状态标志变为 ready。
- promise 对象是异步 Provider,它可以在某一时刻设置共享状态的值。
- future 对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为 ready,然后才能获取共享状态的值。
更多内容请参考:C++11
六、future与promise配合使用示例
下面看一个 future 与 promise 配合使用的示例:
#include<iostream>
#include<thread>
#include<mutex>
#include<atomic>
#include<future> //std::future std::promise
void fun(int x, int y, std::promise<int>& promiseObj) {
promiseObj.set_value(x + y);
}
int main()
{
int a = 10;
int b = 8;
// 声明一个promise类
std::promise<int> promiseObj;
// 将future和promise关联
std::future<int> futureObj = promiseObj.get_future();
// 模板传参的时候使用ref,否则传参失败
std::thread t(fun, a, b, std::ref(promiseObj));
t.join();
// 获取线程的"返回值"
int sum = futureObj.get();
std::cout << "sum=" << sum << std::endl; // 输出:18
std::system("pause");
return 0;
}
参考:
c++11多线程编程(八):std::future , std::promise和线程的返回值