std::future和std::promise详解(原理、应用、源码)

2024-09-03 14:12

本文主要是介绍std::future和std::promise详解(原理、应用、源码),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在编程实践中,我们常常需要使用异步调用。通过异步调用,我们可以将一些耗时、阻塞的任务交给其他线程来执行,从而保证当前线程的快速响应能力。还有一些场景可以通过将一个任务,分成多个部分然后将这部分交给多个线程来进行并发执行,从而完成任务的快速计算执行,提高应用性能。这里就需要用到异步调用的概念

异步调用,就是当前线程将一个任务交给另外一个线程来进行执行,当前线程会接着执行当前任务,不需要等待这个交付给其他线程执行的任务的结果,直到其在未来的某一个时刻需要使用这个任务执行结果数据的时候

链接异步操作和异步操作的管道

创建一个异步调用,然后其他线程执行这个异步调用,异步调用执行完成之后会返回一个结果,然后异步调用创建方在需要使用这个异步调用结果的时候,通过某种方式来获取这个异步调用结果。C++针对上述需求,提供了一些类对象来完成上面的过程,本文中讨论的C++提供的两种对象为std::futurestd::promise,二者的关系对应如下

如上图所示,异步调用创建的时候,会返回一个std::future对象实例给异步调用创建方。异步调用执行方持有std::promise对象实例。双方持有的std::promise对象实例和std::future对象实例分别连接一个共享对象,这个共享对象在异步调用创建方和异步调用执行方之间构建了一个信息同步的通道(channel),双方通过这个通道进行异步调用执行情况的信息交互

异步调用执行方访问这个通道是通过自身持有的std::promise实例来向通道中写入值的,异步调用创建方是通过自身持有的std::future对象实例来获取通道中的值的。当异步调用执行方完成异步调用的执行之后,会通过std::promise对象实例向通道中写入异步调用执行的结果值。异步调用创建方通过自身持有的std::future对象实例来获取异步调用结果

异步调用执行方通过std::promise来兑现承诺(promise,承诺调用完成之后在未来某一时刻交付结果),异步调用创建方通过std::future来获取这个未来的值(future,未来兑现的承诺对应的结果值)

示例

下面,我们通过一段程序示例,来看一下上面这个模型对应的程序,程序如下

上述代码行4~6,创建了一个promise对象,并且从该对象实例中获取到了用于获取承诺兑现的值的std::future对象实例,这样就构建了一个异步调用创建方(发起方)和异步调用执行方之间用于传递异步调用结果的数据通道

在异步调用任务中,完成计算之后,通过std::promise::set_value来进行承诺兑现,将异步调用的结果写入通道中(对应代码行13)。异步调用创建方通过std::future中的get方法来获取异步调用的结果(对应代码行18

上面代码对应的终端输出如下

 async result is 21

有一个细节值得注意,上面代码行12中在异步调用任务执行的时候,可以通过sleep_for将当前线程(异步任务执行的线程)休眠了500ms。这里是为了让异步调用的结果在异步调用创建线程获取结果的代码调用(对应代码行18)之后在进行承诺兑现(通过set_value交付异步调用的结果)。也就是说程序执行到代码行18的时候,异步调用的结果还没有设置到通道中。但是最终终端还是正常的打印了结果。这是因为当调用std::future::get方法获取异步调用结果的时候,如果此时异步调用没有执行完成,即没有向通道内写入异步调用的结果(异步调用结果没有ready),此时当前线程会阻塞直到异步调用结果计算完成并且设置

源码实现

通过上面的概述介绍以及示例,我们了解了futurepromise这两个对象的作用和使用,下面就来看看对应的源码实现。首先,我们先看一下promise的源码实现

promise的源码实现

首先,我们先看一下promise的数据成员,代码截图如下。从下图我们可以看出,promise的数据成员只有一个__state_

该数据成员对应的类型是__assoc_state<_RP>*,是一个指针类型。__assoc_state从类型的名上我们可以看出,该指针指向的是一个状态,该状态对象对应的就是promisefuture之间的通道。_Rp为异步操作返回值的类型,也就是说异步该状态对象内部保存并传递异步调用的返回值

下面,我们先看一下promise的默认构造函数的代码。如下图所示,该构造函数比较简单,就是通过new操作符构建了一个关联状态对象__assoc_state<_Rp>,用这个关联对象的地址信息初始化内部的指针变量__state_,完成promise和通道channel(即__assoc_state实例)的链接

下面,我们看一下get_future的源码实现,代码如下。首先在代码行5中判断当前promise中是否关联了关联状态对象实例(通过判断__state_是否为空),如果未关联则抛出异常(对应代码行6)。如果关联了关联状态对象,则通过该关联对象的地址信息来构建future对象,返回一个链接该关联状态对象的future对象实例

下面,我们看一下set_value的源码实现,代码如下。有两个版本的set_value(代码行3和代码行14)内部逻辑是一样的,区别在于前者是对应于左值版本,后者是对应于右值版本。下面我们以左值版本为例,代码行5~6中先判断了一下当前promise实例是否链接了关联对象实例,如果未链接关联对象实例则抛出异常。如果关联了关联对象实例,则调用关联对象实例的set_value成员方法,将__r实参传入,完成将异步调用结果写入关联状态对象中(即所链接的通道中)

下面,我们看一下future析构函数的源码实现,代码如下。代码行4如果当前promise链接了关联状态对象,则需要在析构的时候处理关联状态对象的信息

  • 代码行6__state_->has_value()用来判断当前关联状态对象中是否存有异步调用结果信息(即是否被设置值或者异常信息,异常信息后面讨论);__state_->use_count()>1用来判断当前关联状态对象中是否有被其他对象关联(在此处为是否有其他future对象关联);此处if内的整体条件判断是用来判断当前关联状态对象中在是否被其他future对象所关联的情况下,没有被设定异步调用的结果的相关信息,如果条件成立执行代码行7~9的代码,这两行代码是抛出一个broken_promise的异常,用来表征当前promise是一个坏的(brokenpromise对象(没有兑现承诺)
  • 代码行10:调用__state_->__release_shared()来释放对关联状态对象的链接,其背后的逻辑类似于shared_ptr中的引用计数,当所有链接关联状态对象的对象都释放链接的时候,该关联状态对象会进行自身资源的释放,占用内存的归还,这部分我们会在后面进行展开讨论

future的源码实现

惯例首先我们看一下future的数据成员,代码截图如下所示。和promise一样,其内部仅仅有一个成员变量__state_,该成员变量是一个指针,用来保存其链接的状态对象的地址信息

通过promisefuture类的内部成员,我们可以看出这两个类都是通过内部的__state_指针完成和通道的关联,这里的通道对应的实现就是__assoc_state这个模版类

下面我们看一下这个类对应的构造函数,代码截图如下图所示。该构造函数接受一个关联状态对象的地址信息,然后使用该地址信息来初始化内部的__state_变量,完成当前future到关联状态对象的链接

完成状态对象信息的链接之后,通过代码行5,调用关联状态对象的__attach_future成员函数,完成future和关联状态对象的链接(attach)。该成员函数的内部具体细节将在__assoc_state的讨论中展开

下面,我们看一下future对象的get函数的代码实现,代码截图如下所示。

  • 代码行5:创建一个名为__unique_ptr的临时对象,并且将__state_托管给该对象,该对象是一个临时对象,将会在get函数运行结束的时候进行释放,当该对象释放的时候,将会对通过__release_shared_count函数来对__state_指向的关联状态对象进行相关的处理
  • 代码行6~7:将当前__state_的值赋值给临时变量__s,将__state_进行置空,完成future对状态对象的链接的断开
  • 代码行8:通过调用__s->move()来完成对关联状态中异步调用结果的获取,获取完成之后将该值作为get函数的返回值进行返回。同时__s->move()函数不仅仅是单纯的获取异步调用结果,同时还会判断是否有异步调用的值,如果异步调用没有完成,则会阻塞在move函数中,等待异步调用完成

下面我们看一下__release_shared_count的代码定义,代码截图如下所示。__release_shared_count是一个仿函数,其内部代码行3的函数调用操作符的重载就是该仿函数的逻辑,对传入的关联状态对象调用__release_shared,完成对于内部引用计数的递减,当引用计数递减到-1的时候,会对关联状态对象进行资源释放

上面的函数调用重载函数的形参是__shared_count*类型,之所以不是__assoc_state*类型,是因为这里是一个多态,__assoc_state__shared_count的派生类,这里将在后面讨论__assoc_state的时候展开讨论

上面我们看到在调用get方法之后,future断开了和关联状态对象的链接,这说明future对象只能调用一次get方法来获取,如果多次调用,其内部__state_将为空指针,则会因为对空指针调用move方法,造成未定义行为

下面,我们来看一下析构函数的代码实现,代码截图如下所示。如下图所示,如果当前future有链接状态关联对象,则调用其__release_shared成员函数,从而对其内部引用计数进行递减,当其内部引用计数递减至-1的时候,将完成自身占用资源的释放

上面我们已经探讨了futurepromise的相关的代码实现,下面我们将讨论一下两者中间进行数据传递的管道的代码实现

关联状态对象的代码实现

clang版本的关联对象的实现是通过3层继承来实现的,没层继承都对应了一个功能实现的职责,按照继承树的层级从上至下,这3层继承分别如下:

  1. __shared_count:引用计数类,该类用来保存引用计数信息,通过该类内部的引用计数信息来实现自身对象生命周期的管理。用来跟踪链接到自身的promisefuture对象的数量,当没有任何对象链接自身的时候,进行自身资源的释放
  2. __assoc_sub_state:负责保存管理当前关联状态对象的状态(constructed/attached/ready/deferred,随后展开讨论),进行线程之间的同步
  3. __assoc_state:负责保存异步操作返回值,并且做最终的封装提供最终的接口给futurepromise来使用

clang版本的在很多标准库的实现中都采用了这种通过继承,在每个继承的层级设置不同的职责,最终通过继承树组装起来的方式。而msvc版本的则更多的采用了组合而非继承的方式。不知道为啥,挺有意思的

__assoc_state

下面,我们通过最下面派生类来进行一步一步的讨论,首先下面看一下__assoc_state类的数据成员

如上代码所示,该类只有一个在属于当前层级的成员变量,该成员变量位于代码行8,该成员变量是用来保存异步调用结果的,类型是_Up该类型是一个类型别名,其别名定义在代码行6,其是通过返回值类型_Rp构建了一个对应的内存对齐的数据类型_Up。这里主要是为了访问效率,内存对齐不在本次讨论范围,这里不展开讨论。理解上可以认为__value_就相当于是异步操作返回值的类型

下面,我们看一下该继承层级上该类的内部成员函数,首先我们看一下set_value这个成员方法,代码定义如下

该函数的形参__arg是用来接受待设置到关联状态对象内部的异步调用结果的值,该函数是一个万能引用,从而根据传入的实参来自动的进行判断传入的实参是以左值引用的方式传入的还是以右值的方式传入的。从而在代码行10的位置通过std::forward进行完美转发,从而决定是以移动的方式还是拷贝的方式进行实参的接收

  • 代码行7:实例化一个局部的unique_lock类型__lk临时对象,通过RAII技术来持有内部的__mut_互斥锁,当该临时对象离开作用域的时候,即函数结束的时候,会在析构的时候对互斥锁__mut_进行释放。该__mut_锁是用来保存内部数据在并发操作的时的多线程并发安全性的。比如异步操作创建线程和异步操作执行线程同时分别通过futurepromise来从通道中读取和写入值的时候
  • 代码行8:通过调用成员函数_has_value来判断当前关联状态对象是否已经被设定值了,即设定了异步调用结果信息,如果已经设定了,此时执行代码行9,抛出promise already satisfied异常,用来告诉调用方该promise早已经被兑现了
  • 代码行10:通过placement new操作符,在__value_上以函数形参__arg的值,构建异步调用返回值对象。这里之所以采用placement new是因为用来保存返回值的内存早就已经存在,即内部的__value_成员变量,这行代码要做的只是在这个内存上进行对象的构建,而不需要通过new那样先申请内存,然后在申请的内存上进行对象的构建
  • 代码行11:设置当前关联对象的状态为constructed,表征当前关联状态对象内部已经构建了异步调用的结果的值;以及设置当前关联对象的状态为ready,用来表征当前关联状态对象已经处于就绪(ready)状态,future对象可以通过get立刻获取到异步调用的结果信息
  • 代码行12:调用内部的__cv_条件变量(conditional variable),用来通知因为在关联状态对象没有ready的时候因为调用future对象的get方法而阻塞的线程,唤醒这些线程继续获取异步调用的结果信息

下面我们讨论一下上面提到的关联状态对象的内部成员函数move,为了方便阅读,我们把调用该函数的future的成员函数重新贴出如下

move函数是用来获取关联状态对象内部保存的异步调用的值的,move的含义是将该值从关联状态对象中移动出来,然后作为get函数的返回值返回给调用方,完成异步调用结果的传递

  • 代码行5:声明一个unique_lock类型的临时对象__lk来对内部的__mut_互斥量进行上锁,从而对关联状态对象内部的数据进行并发保护
  • 代码行6:通过调用__sub_wait成员函数(该成员函数是从基类继承来的),该成员函数内部对关联状态对象的值的状态进行判断,如果处于就绪状态,即已经可以允许future来获取值的状态,则会接着向下执行。如果没有处于就绪状态,则会在此处阻塞知道状态就绪,这就是wait的含义
  • 代码行7~8:判断关联状态内部是否保存异步调用执行过程中抛出的异常信息,如果有通过代码行8进行该异常的重新抛出,完成异常从异步调用线程到异步调用创建方线程的传递。此处为异常安全的设计需求,后续展开讨论
  • 代码行9:以移动的方式返回关联状态对象内部的异步调用结果

该关联对象还有一个函数__on_zero_shared,该函数是对其基类__shared_count的同名虚函数的实现(重写)。该函数在内部引用计数递减至-1的时候,即没有任何promise或者future对象链接该关联状态对象的时候,调用该函数,完成对自身资源的释放

代码行5~6:判断当前关联对象内部是否构建了保存异步调用对象返回值的变量,如果构建了在代码行6调用其析构函数。注意这里是调用析构函数,并没有释放内存,因为该值是保存在关联状态对象内部成员变量中,会随着状态对象自身的释放而释放

代码行7:对自身占用的资源进行释放

__assoc_sub_state

按照惯例,我们先看一下该类的数据成员,通过数据成员来看一下该类具体的功能

  • 代码行5:用来保存异步调用时抛出的异常信息,该异常信息会在异步操作创建方线程调用future::get方法的时候,重新在该线程抛出,从而完成异常信息在异步调用线程和其创建线程之间传递
  • 代码行6:互斥量,用来实现关联状态对象内部数据的线程安全,保证并发安全
  • 代码行7:条件变量,用来实现异步调用线程和异步调用创建方线程之间的同步
  • 代码行8:用来记录关联状态对象当前的状态,下面我们先展开说一下这个状态

__state_是用来被记录当前关联状态对象的状态的,其是按照位来存储的,初始状态下__state_初始值为0,代表所有状态位都为false。下面我们来看一下这些状态位,状态位相关定义的代码如下图所示

状态位的每一位的定义是通过枚举进行定义的,当需要设置某些位的时候直接通过或运算和__state_进行或运算即可

  • __constructed:对应bit0,是用来表示内部已经构建保存了异步调用操作的结果值
  • __future_attached:对应bit1,是用来表示当前关联状态对象已经被future对象所链接(attached
  • ready:用来表示当前关联状态对象处于就绪(ready)状态,链接该对象的future对象可以通过调用get方法来立刻获取到该异步调用的结果相关信息(获取到异步调用的返回值或者异步调用时发生的异常)
  • deferred:用来表示这是一个推迟执行的“异步调用”,这块后边章节会单独介绍,这种推迟调用,是当前不即可求值也不会在其他线程进行并发求值,而是在使用future进行get的时候,在get的时候,在get调用的线程进行同步求值

这里可能会好奇,这个__constructedready这两个状态,可能会有疑问前者状态被置位的时候,说明了该关联状态对象已经被设置了异步调用结果信息,此时不就是就绪状态吗?

其实不是这样的,有的时候设置了异步调用结果之后,并不想使得当前的关联状态对象变成就绪状态,即不想使得此时其他线程通过future::get方法获取到异步调用结果信息,set_value方法是设置了值之后就进入了就绪状态,但是下面的这个接口就不是

上面的代码是先设置异步调用结果值到关联状态对象中(对应代码行9),然后在代码行10设置关联状态对象中的__constructed标识位,表征当前已经设置了异步调用结果信息。但是并没有设置ready状态。代码行11的作用是设置线程退出的时候钩子,当线程退出的时候再将该状态对象转换为就绪状态

上面set_value_at_exit函数是为了在异步操作执行的线程退出的时候,在将关联状态对象转变为就绪状态,即异步操作创建方在该异步操作执行线程退出的时候才能获取到该异步调用的结果信息,这里其实是提供了另一种线程同步的方法

下面,我们看一下上面讨论__assoc_state的时候,讨论接口的时候内部涉及到的该继承层级被使用的函数,首先我们先看一下上面提到的__has_value成员函数,该函数的源码实现如下图所示

如上图所示,该函数是用来判断当前关联状态对象内部有没有保存异步调用执行结果的信息,注意这里面异步调用结果信息可以是当异步调用成功执行之后得到的返回值,也可以是异步调用没有执行完毕执行过程中抛出的异常。所以该函数的内部实现是通过查看__state___constructed的状态位或者内部__exception_是否保存了异常信息

下面,我们在看一下其他的成员函数,首先我们看一下__attach_future成员函数。在看该成员函数的具体实现,我们先看一下该成员函数的使用,该成员函数是用来将future链接到该关联状态对象的信息登记到该关联状态对象中的,当future对象构建的时候,会调用该函数,如下图所示

future链接一个关联状态对象的时候,会调用该关联状态对象的成员函数__attach_future来通知关联状态对象,有一个future链接他,下面我们看一下该成员函数内部实现,如下图所示

  • 代码行3:通过实例化一个临时lock_guard的对象__lk来进行互斥量上锁,用来保证关联状态对象的并发安全
  • 代码行4~6:用来判断当前关联状态对象是否被其他future对象所链接,如果已经有其他future对象链接,则执行代码行6抛出异常,代表该关联状态对象早已经被链接
  • 代码行7:调用__add_shared成员函数,该成员函数来自于__shared_count,其作用是递增内部计数。表示当前增加了一个future链接到了该对象,这个引用计数是为了管理关联状态对象的生命周期
  • 代码行8:将内部__state_变量中的记录关联状态对象是否被future对象所链接的状态位置位

下面,我们看一下在__assoc_state章节,讨论move函数时,该函数内部调用到的__sub_wait的函数,为了方便阅读,我们先将move的调用首先贴出如下

如上面代码行6,在进行返回关联状态对象的内部存储的异步调用的结果信息之前,需要先调用__sub_wait函数,如果当前关联状态对象没有就绪(ready)的时候,需要在该函数内部阻塞等待,下面我们就看一下该函数的内部实现

  • 代码行4:判断当前关联状态对象是否处于就绪状态,如果处于就绪状态,函数直接返回。代表外层的future::get方法可以继续获取内部的值
  • 代码行6:通过__state_判断当前关联状态对象是否是推迟求值,如果是执行代码行8~10,如果不是执行代码行13~14
  • 代码行8~10:首先清除内部存储的是否是推迟求值的标志位,然后释放用来保护关联状态变量内部数据并发安全的锁,然后在代码行10执行推迟求值操作(相当于异步操作的函数),使得“异步操作”调用方在调用future::get方法时在当前线程中执行这个求值操作。这里在求值执行之前之所以要解锁,是因为这里的锁事为了保护关联状态对象内部自身的状态,而不是这个求值调用本身
  • 代码行13~14:如果不是推迟求值,而是异步调用,此时未处于就绪状态,需要调用条件变量的wait函数,等待就绪通知

__shared_count

这个对像内部维护了一个引用计数,当关联状态对象被一个promisefuture对象所链接的时候,会通过调用其内部的__add_shared对引用计数进行递增1操作。当某个对象断开链接的时候,会调用其内部的__release_shared对引用计数进行递减1操作。当引用计数的递减到-1的时候,释放关联状态对象。为什么是-1因为当第一个promise或者future对象链接该关联状态对象的时候,引用计数对应的值为0

限于篇幅此处就不展开讲了,要想知道该类的细节可以参考下面文章,shared_point对象内部就是用该类来管理内部引用计数的

孙梓轩:源码解析:shared_ptr是如何实现共享对象所有权的?8 赞同 · 0 评论文章​编辑

future和promise之间的并发安全和线程同步

从上面讨论我们知道,异步操作创建方和异步操作执行方都是是通过其各自的future/promise对象共同链接的关联状态对象进行同步和信息传递的

通过上面对于关联状态对象的讨论,我们可以看出其API的内部是通过互斥量对内部状态进行保护,从而实现了线程安全。通过条件变量来实现的线程同步。并且一个关联状态对象在一个时刻只能被一个future和一个promise所链接,如果被多个链接则会抛出异常

CPP Reference中有一句话刻意描述了这句话,截图如下所示

上面的最后一段话,中文含义是:注意std::future所引用的共享状态是不和任何其他异步操作所返回的future对象所共享的(与之对应的是std::shared_future

并且异步操作创建方只能调用一次future::get来获取异步调用的结果信息,这是因为future::get函数内部,会断开future对象与关联状态对象之间的链接,如下代码行5

future对象析构时如果没有调用get接口会阻塞当前进程吗?

从我们前面讨论的future源码实现中,可以看到,在当前clang版本(不一定是最新,感兴趣的可以去下载最新的实现看看)的实现中是不阻塞的,为了方便阅读,重新贴出析构函数的源码实现如下

其内部先判断了当前有没有链接关联状态对象,如果链接了调用其__release_shared成员函数,该成员函数是非阻塞的,其内部仅仅是原子的对引用计数进行递减

但是,你可能从其他的帖子中听过future的坑,future如果没有调用get会阻塞异步操作创建方的线程,在future被析构的时候。下面我们就到 CPP Reference上看一下C++标准关于此处的规定,网站截图如下

其中文含义为:

释放任何共享状态(就是我们上面提到的关联状态对象)。这意味着:

  • 如果当前对象是最后一个链接到该共享状态的对象,则这个共享对象将会被销毁
  • 当前对象放弃其对共享状态的引用(即断开和关联状态对象的链接)
  • 此操作不会因为共享状态不处于就绪状态而阻塞,除非当下面所有条件被满足
  1. 这个共享状态是通过调用std::async创建的
  2. 共享状态目前没有处于就绪状态
  3. 当前对象是最后一个引用该共享状态对象的对象

实际上,此操作仅仅在任务的启动策略是std::launch::async才会产生阻塞,因为这是被运行时操作系统所决定的或者是因为是在调用std::async所指定的

异常安全

promisefuture进行异步调用结果信息同步的这个机制,从代码上的设计就是异常安全的。这里面的异常安全设计主要在两个方向:

  1. 首先相关的API接口和函数实现需要是异常安全的,即软件内部如果出现异常的时候,函数异常返回的时候不能出现内存泄漏,资源未正确释放等异常情况
  2. 其次异步调用的时候,如果产生异常可以通过promisefuture以及关联状态对象,将异常从异步操作执行线程传递到异步操作创建方所在线程中

下面,我们就逐步来看上面亮点。对于第1点为了保证在函数异常返回的时候,不能出现资源泄露。通常为了实现这点有一个有效的途径就是在函数内使用RAII计数来管理资源

RAII,资源申请即初始化(Resource Acquisition Is Initialization),通过对象创建(申请)的时候会自动调用构造函数,从而在构造函数中对被管理资源进行占有操作(比如在构造函数中进行互斥量上锁,即锁的占有);对象销毁的时候会自动调用析构函数,从而在析构函数完成对被管理资源的释放(比如在构造函数中进行互斥量解锁,即锁的释放)

下面,我们以future::get函数为例,来探讨该函数实现对于异常安全的考量,代码截图如下

上面代码行5,这里就是采用了RAII的计数,实例化了一个__对象。将__state_托管给该unique_ptr指针,这个unique_ptr是独占所有权的共享指针,其构造函数没有对资源进行操作,在析构函数对资源进行了释放,通过第2个模版参数传入自定义资源释放操作,在该函数中会对__state_进行引用计数递减。即当函数执行出现异常返回的时候,会自动对关联状态对象内部的引用计数进行递减操作

下面,我们看一下这个函数调用链中是否有有抛出异常的代码。上面抛出异常的代码就在代码行8move函数的调用中。下面我们重新贴出该函数的代码实现,如下所示

上面代码行7~8,当获取异步调用结果信息的时候,发现了目前关联状态对象被设置了异常信息的时候,在代码行8中重新抛出该异常

下面,我们探讨一下异步操作执行方线程线程传递给异步操作创建方的代码实现。在讨论实现之前,我们来讨论一下,为什么要进行一场信息传递

当一个函数抛出异常的时候,该异常会沿着调用链逐级向上返回,当异常返回到调用链的最高层级这个逐级向上返回的过程中,没有对异常进行捕捉处理,此时会导致程序终止。这里面异常是沿着调用链逐级向上传递的,这也就说明异常只能在产生异常的线程内部进行逐级向上传递(因为函数调用链的各个层级必然属于一个线程)

而这里,构建一个futurepromise传递异常的通道的原因是由于异步调用是在另外一个线程执行异步操作,但是在另外一个线程进行异步调用结果信息获取,如果异步调用执行过程中出现异常,那么此时这个异常信息也是异步调用执行结果的一种表现形式,那么此时如果不捕获这异常通过构建的通道传递给异步调用结果信息使用线程,这个异常将会在异步调用线程被传递,在异步调用结果使用线程中无法捕捉这一异常信息(因为不会传递到该线程)。那么该线程就无法了解异步调用操作异常返回这一信息

下面,我们看一下实例代码,来看一下当异步调用产生异常的时候,如何进行传递。限于篇幅讨论,这里仅仅贴出代码不展开讨论了,代码和运行结果的截图如下

终端输出如下所示

 this is a test exception

这篇关于std::future和std::promise详解(原理、应用、源码)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1133179

相关文章

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu1394(线段树点更新的应用)

题意:求一个序列经过一定的操作得到的序列的最小逆序数 这题会用到逆序数的一个性质,在0到n-1这些数字组成的乱序排列,将第一个数字A移到最后一位,得到的逆序数为res-a+(n-a-1) 知道上面的知识点后,可以用暴力来解 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#in

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

zoj3820(树的直径的应用)

题意:在一颗树上找两个点,使得所有点到选择与其更近的一个点的距离的最大值最小。 思路:如果是选择一个点的话,那么点就是直径的中点。现在考虑两个点的情况,先求树的直径,再把直径最中间的边去掉,再求剩下的两个子树中直径的中点。 代码如下: #include <stdio.h>#include <string.h>#include <algorithm>#include <map>#

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和