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)

  1. 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: 

posted on 2024-02-04 19:33  tsecer  阅读(387)  评论(0编辑  收藏  举报

导航