C++中promise和future初认识
future/promise
future提供了一个基于数据(future模板类型)的异步概念:对于一个类型T,可以在以后通过get接口获得这个类型T的变量。或者打个不太恰当的比方,当你获得一个future对象时,就获得了一个消费券(consumer):拿着这张券可以兑换(get)一个T类型的结果(如果数据未就绪的话会阻塞等待)。至于这个类型是从哪里来的future对象本身并不关系,它是future提供者的职责。
事实上,从future的文档(为了方便阅读单独拷贝一份)就可以看到,除了promise之外,还有std::async、std::packaged_task也可以提供future。promise只是抽象了一种数据结构(但是没有提供具体如何异步),这种数据结构支持set_value和future::get的时序/等待/状态/异常处理等常用逻辑。当数据就绪之后,直接通过promise::set_value生产数据即可。至于这个set_value从哪里来,promise本身也不关系(它只是一个数据结构),需要具体生产者负责。
#include <future>
#include <iostream>
#include <thread>
int main()
{
// future from a packaged_task
std::packaged_task<int()> task([]{ return 7; }); // wrap the function
std::future<int> f1 = task.get_future(); // get a future
std::thread t(std::move(task)); // launch on a thread
// future from an async()
std::future<int> f2 = std::async(std::launch::async, []{ return 8; });
// future from a promise
std::promise<int> p;
std::future<int> f3 = p.get_future();
std::thread([&p]{ p.set_value_at_thread_exit(9); }).detach();
std::cout << "Waiting..." << std::flush;
f1.wait();
f2.wait();
f3.wait();
std::cout << "Done!\nResults are: "
<< f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
t.join();
}
再笼统的说:promise和future大致相当于一个管道(Unix中pipe)文件的两个两端,分别是输入和读取数据的两端,具体数据如何产生,消费的数据如何处理,这种机制本身并不关心。
解耦
- 使用场景
future并不是一定依赖于promise的,其它的一些场景下也可以生成future。
- producer/consumer
promise中有set_value接口,而future中有get接口;这个描述还有一个对应的反面:promise没有get(value)接口,future没有set_value接口。
从逻辑上拆分之后,不通过类型的职责更加单一,也更易于理解使用场景。
- 生存期
当future生成(例如promise::get_future)之后,future可以从它的原始“生产地”中完全脱离出来。例如,如果promise在set_value之后对象本身析构。
问题
如果promise设置了值(set_value)之后,在future执行get之前析构,此时future的get还有效吗?
SO的答案说明:如果在销毁之前给promise提供了value,那么对应的std::future可以毫无压力的获得设置值。
If you provide a value to the std::promise before destroying it, the associated std::future will be able to retrieve it just fine. It does not depend on std::promise still existing. If you failed to provide a value to std::promise before destroying it, trying to get a result from the std::future will throw std::future_error but it is also well defined.
查看源代码中promise的析构函数,在析构的时候,判断如果_M_future非空,并且_M_future指向内容有多个shared_ptr引用,那么会设置_M_future为_M_break_promise。这样的话,通过future来get的时候,应该会抛出异常才对。到底是哪里出了问题呢?
~promise()
{
if (static_cast<bool>(_M_future) && !_M_future.unique())
_M_future->_M_break_promise(std::move(_M_storage));
}
测试
从测试来看,在promise析构之后,get_futre().get()的确可以毫无障碍的获得promise析构前设置的value。
tsecer@harry: cat dest_promise.cpp
#include <future>
#include <stdio.h>
int main(int argc, const char *argv[])
{
std::promise<int> *pp = new std::promise<int>;
std::future<int> of = pp->get_future();
pp->set_value(1234);
delete pp;
printf("future.get is %d\n", of.get());
return 0;
}
tsecer@harry: g++ dest_promise.cpp -g -lpthread
tsecer@harry: ./a.out
future.get is 1234
tsecer@harry:
原因
由于问题明显而必现,在调试的帮助下很容易知道问题的原因:在执行promise析构函数时,_M_storage内容已经为空,而在_M_break_promise函数中,会判断指针是否为空,在指针为空的情况下并不会设置broken_promise。
// Abandon this shared state.
void
_M_break_promise(_Ptr_type __res)
{
if (static_cast<bool>(__res))
{
__res->_M_error =
make_exception_ptr(future_error(future_errc::broken_promise));
// This function is only called when the last asynchronous result
// provider is abandoning this shared state, so noone can be
// trying to make the shared state ready at the same time, and
// we can access _M_result directly instead of through call_once.
_M_result.swap(__res);
// Use release MO to synchronize with observers of the ready state.
_M_status._M_store_notify_all(_Status::__ready,
memory_order_release);
}
}
那么_M_storage是何时清空的呢?
由于demo代码比较简单,可以跟踪到是在set_value的时候,经过多层调用,最后会执行到这里的operator()() 函数,其中有一个std::move(_M_promise->_M_storage),在_M_storage是一个unique_ptr的情况下,move操作会导致复制的源内容被清空。
// set void
template<typename _Res>
struct _Setter<_Res, void>
{
static_assert(is_void<_Res>::value, "Only used for promise<void>");
typename promise<_Res>::_Ptr_type operator()() const
{ return std::move(_M_promise->_M_storage); }
promise<_Res>* _M_promise;
};
unique_ptr,明确说明了如果是一个右值引用,这种构造函数将会转移所有权(by transferring ownership)
- Constructs a unique_ptr by transferring ownership from u to *this and stores the null pointer in u. This constructor only participates in overload resolution if std::is_move_constructible
::value is true. If Deleter is not a reference type, requires that it is nothrow-MoveConstructible (if Deleter is a reference, get_deleter() and u.get_deleter() after move construction reference the same value).
回顾promise的结构及get_future
- 结构
promise包括两个结构,一个是通过shared_ptr指向的future结构(_M_future),一个是通过unique_ptr指向的类型(promise参数中的类型)数据结构(_M_storage字段)。
/// Primary template for promise
template<typename _Res>
class promise
{
///...
typedef __future_base::_Ptr<_Res_type> _Ptr_type;
///...
shared_ptr<_State> _M_future;
_Ptr_type _M_storage;
///...
///...
}
其中的_Ptr_type是一个unique_ptr类型
/// Base class and enclosing scope.
struct __future_base
{
/// A unique_ptr for result objects.
template<typename _Res>
using _Ptr = unique_ptr<_Res, _Result_base::_Deleter>;
}
future内容如下
// Base class for various types of shared state created by an
// asynchronous provider (such as a std::promise) and shared with one
// or more associated futures.
class _State_baseV2
{
typedef _Ptr<_Result_base> _Ptr_type;
enum _Status : unsigned {
__not_ready,
__ready
};
_Ptr_type _M_result;
__atomic_futex_unsigned<> _M_status;
atomic_flag _M_retrieved = ATOMIC_FLAG_INIT;
once_flag _M_once;
可以看到其中除了同步、状态之外,还有一个指向类型的_M_result字段,它在执行_M_future初始化的时候并没有和_M_storage关联起来。
- 分配
在promise的缺省构造函数中会为分配对应的存储空间。
promise()
: _M_future(std::make_shared<_State>()),
_M_storage(new _Res_type())
{ }
- 所有权
正如前面所说,当自行set_value时,会设置std::move(_M_promise->_M_storage)将_M_storage指向空间的所有权转让出去,对于set_value来说,就是转让给了_M_future结构的_Ptr_type字段。
当通过promise::get_future获得future时,会获得一个指向_State的指针。由于这个字段是shared_ptr,所以get_future之后返回的future对象和这个promise(以shared_ptr)形式共享这个future成员。
总起来说,它们的生命期维护关系为: _M_future是一个promise和future共同维护的shared_ptr,两个结构中的任意一个销毁都不会导致指向内容释放;结构中的_Ptr_type是一个unique_ptr,当_M_future析构的时候才会释放。
如何出现析构中的Broken promise
在确定了_M_storage是由于set_value而清空的之后,构造Broken promise的方法就很简单了:就是不调用set_value即可。
tsecer@harry: cat no_set_value_dest_promise.cpp
#include <future>
#include <stdio.h>
int main(int argc, const char *argv[])
{
std::promise<int> *pp = new std::promise<int>;
std::future<int> of = pp->get_future();
//pp->set_value(1234);
delete pp;
printf("future.get is %d\n", of.get());
return 0;
}
tsecer@harry: g++ -g no_set_value_dest_promise.cpp -lpthread
tsecer@harry: ./a.out
terminate called after throwing an instance of 'std::future_error'
what(): std::future_error: Broken promise
已放弃 (core dumped)
tsecer@harry: