并发编程(8)—— std::async、std::future 源码解析
文章目录
- 八、day8
- 1. std::async
- 2. std::future
- 2.1 wait()
- 2.2 get()
八、day8
之前说过,std::async内部的处理逻辑和std::thread相似,而且std::async和std::future有密不可分的联系。今天,通过对std::async和std::future源码进行解析,了解二者的处理逻辑和关系。
源码均基于 MSVC 实现
参考:
- 博主恋恋风辰的个人博客
- up主mq白cpp的个人仓库
1. std::async
std::async有两种重载实现:
template <class _Fty, class... _ArgTypes>
_NODISCARD future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>> async(launch _Policy, _Fty&& _Fnarg, _ArgTypes&&... _Args) {// manages a callable object launched with supplied policyusing _Ret = _Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>;using _Ptype = typename _P_arg_type<_Ret>::type;_Promise<_Ptype> _Pr(_Get_associated_state<_Ret>(_Policy, _Fake_no_copy_callable_adapter<_Fty, _ArgTypes...>(_STD forward<_Fty>(_Fnarg), _STD forward<_ArgTypes>(_Args)...)));return future<_Ret>(_Pr._Get_state_for_future(), _Nil());
}template <class _Fty, class... _ArgTypes>
_NODISCARD future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>> async(_Fty&& _Fnarg, _ArgTypes&&... _Args) {// manages a callable object launched with default policyreturn _STD async(launch::async | launch::deferred, _STD forward<_Fty>(_Fnarg), _STD forward<_ArgTypes>(_Args)...);
}
第一种重载需要显式指定启动策略,也就是我们之前说的std::launch::async、std::launch::deferred和std::launch::async | std::launch::deferred;第二种重载在使用默认策略时会被调用(也就是只传递可调用对象和参数而不传递启动策略),在内部会调用第一种重载并传入一个std::launch::async | std::launch::deferred策略,并将参数全部转发。
我们只需要着重关注第一种重载即可:
-
模板参数和函数体外部信息:
_Fty:可调用对象的类型_ArgTypes:可调用对象所需的参数类型_NODISCARD:宏,用于标记该函数的返回值不应被忽略
-
返回类型:
future<_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>>其实就是返回一个
std::future对象,_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>是std::invoke对给定的可调用对象_Fnarg和参数_Args...执行后返回的类型,其实也就是通过_Invoke_result_t从_Fty和_ArgTypes...中推导出的返回类型。我们之前在
thread源码解析中说过std::invoke内部其实是调用_Call函数,_Call函数负责提供参数并调用传入的可调用对象。我们可以把
_Invoke_result_t看作是一个对std::invoke的结果类型的封装,std::invoke是一个工具,可以调用可调用对象并返回其结果。_Invoke_result_t提供了一种方式来“推导”出这个结果类型。这个类型萃取工具通常长这样(简化版):
template <typename _Callable, typename... _Args> struct _Invoke_result_t {using type = decltype(std::invoke(std::declval<_Callable>(), std::declval<_Args>()...)); };上述代码通过
std::invoke来推导(decltype)_Callable(即可调用对象)在给定参数_Args...上执行后的返回类型。换句话说,_Invoke_result_t<_Fty, _ArgTypes...>的type成员类型就是可调用对象在调用后的返回类型。值得注意的是,所有类型在传递前都进行了
decay处理,也就是将cv和const修饰符去掉,默认按值传递与std::thread的行为一致。 -
形参:
future<_Ret> async(launch _Policy, _Fty&& _Fnarg, _ArgTypes&&... _Args) {}launch _Policy: 表示任务的执行策略,可以是launch::async(表示异步执行)或launch::deferred(表示延迟执行),或者std::launch::async | std::launch::deferred_Fty&& _Fnarg: 可调用对象,通过完美转发机制将其转发给实际的异步任务_ArgTypes&&... _Args: 调用该可调用对象时所需的参数,同样通过完美转发机制进行转发
-
_Ret和_Ptype:-
_Ret就算我们在返回类型中说到的_Invoke_result_t<decay_t<_Fty>, decay_t<_ArgTypes>...>,表示可调用对象的返回类型; -
using _Ptype = typename _P_arg_type<_Ret>::type:_Ptype的定义在大多数情况下和_Ret是相同的,类模板_P_arg_type只是为了处理引用类型以及 void 的情况,参见_P_arg_type的实现:template <class _Fret> struct _P_arg_type { // type for functions returning Tusing type = _Fret; };template <class _Fret> struct _P_arg_type<_Fret&> { // type for functions returning reference to Tusing type = _Fret*; };template <> struct _P_arg_type<void> { // type for functions returning voidusing type = int; };为什么需要 _Ptype ?
在异步任务的实现中,
std::promise是用于将结果与std::future绑定的对象。std::promise的模板参数通常是可调用对象返回值的类型。在std::async函数中,我们需要创建一个std::promise对象来存储任务的结果,因此我们需要计算出正确的承诺类型(promise type)。也就是说,定义_Ptype是为了配合后面_Promise的使用,确保任务的结果可以通过std::future获取。-
_Ret是任务返回的类型(由_Invoke_result_t推导出)。 -
_Ptype就是这个返回类型的承诺类型。也就是说,**_Ptype是std::promise的模板参数类型,**表示这个任务结果的类型。
_Ptype的定义在大多数情况下和_Ret是相同的,都是可调用对象返回值的类型。 -
-
-
_Promise<_Ptype> _Pr:创建一个std::promise对象_Pr,其类型为_Ptype,表示与异步任务的结果相关联的承诺(promise)。_Promise类型我们之前讲过,这里就不在叙述它的作用,关键还在于其存储的数据成员:template <class _Ty> class _Promise { public:_Promise(_Associated_state<_Ty>* _State_ptr) : _State(_State_ptr, false), _Future_retrieved(false) {}_Promise(_Promise&& _Other) : _State(_STD move(_Other._State)), _Future_retrieved(_Other._Future_retrieved) {}_Promise& operator=(_Promise&& _Other) {_State = _STD move(_Other._State);_Future_retrieved = _Other._Future_retrieved;return *this;}~_Promise() noexcept {}void _Swap(_Promise& _Other) {_State._Swap(_Other._State);_STD swap(_Future_retrieved, _Other._Future_retrieved);}const _State_manager<_Ty>& _Get_state() const {return _State;}_State_manager<_Ty>& _Get_state() {return _State;}_State_manager<_Ty>& _Get_state_for_set() {if (!_State.valid()) {_Throw_future_error(make_error_code(future_errc::no_state));}return _State;}_State_manager<_Ty>& _Get_state_for_future() {if (!_State.valid()) {_Throw_future_error(make_error_code(future_errc::no_state));}if (_Future_retrieved) {_Throw_future_error(make_error_code(future_errc::future_already_retrieved));}_Future_retrieved = true;return _State;}bool _Is_valid() const noexcept {return _State.valid();}bool _Is_ready() const {return _State._Is_ready();}bool _Is_ready_at_thread_exit() const {return _State._Is_ready_at_thread_exit();}_Promise(const _Promise&) = delete;_Promise& operator=(const _Promise&) = delete;private:_State_manager<_Ty> _State;bool _Future_retrieved; };注意:
_Promise和std::promise并不是同一个模板类,_Promise是为了提供对std::promise的进一步定制,并不是std::primse本身。std::primise模板类的私有成员是通过_Promise声明的,即// std::primise 的私有成员 private:_Promise<_Ty*> _MyPromise;
_Promise类模板是对_State_manager类模板的包装,并增加了一个表示状态的私有成员_Future_retrieved。private:_State_manager<_Ty> _State;bool _Future_retrieved;状态成员用于跟踪
_Promise是否已经调用过_Get_state_for_future()成员函数;它默认为false,在第一次调用_Get_state_for_future()成员函数时被置为true,如果二次调用,就会抛出future_errc::future_already_retrieved异常。_Promise的构造函数接受的不是_State_manager类型的对象,而是_Associated_state类型的指针,用来初始化数据成员_State。_Promise(_Associated_state<_Ty>* _State_ptr) : _State(_State_ptr, false), _Future_retrieved(false) {}这是因为实际上
_State_manager类型只有两个私有成员:Associated_state指针,以及一个状态成员:private:_Associated_state<_Ty>* _Assoc_state;bool _Get_only_once;可以简单理解为
_State_manager是对Associated_state的包装,其中的大部分接口实际上是调用_Assoc_state的成员函数(你们可以去_State_manager的实现源码中查阅,大部分接口其实都是通过调用_Assoc_state实现的)。所以在解析
std::async源码之前,我们必须对Associated_state有一个清晰的了解:public:_Ty _Result;exception_ptr _Exception;mutex _Mtx;condition_variable _Cond;bool _Retrieved;int _Ready;bool _Ready_at_thread_exit;bool _Has_stored_result;bool _Running;这是
Associated_state模板类主要的成员变量(我没有全部列上去,只列了主要的),其中,最为重要的三个变量是:异常指针、互斥量、条件变量。其实,
_Associated_state模板类负责管理异步任务的状态,包括结果的存储、异常的处理以及任务完成的通知。它是实现std::future和std::promise的核心组件之一,通过_State_manager和_Promise类模板对其进行封装和管理,提供更高级别的接口和功能。
_Promise、_State_manager、_Associated_state之间的包含关系如上述结构所示。 -
初始化
_Promise对象:_Promise<_Ptype> _Pr(_Get_associated_state<_Ret>(_Policy, _Fake_no_copy_callable_adapter<_Fty, _ArgTypes...>(_STD forward<_Fty>(_Fnarg), _STD forward<_ArgTypes>(_Args)...)) );这是一个函数调用,将我们
std::async的参数全部转发给它。- 首先将参数
_Fnarg(可调用对象)和_Args...(传入可调用对象的参数包) 通过std::forward转发给_Fake_no_copy_callable_adapter。 - 然后,
_Fake_no_copy_callable_adapter创建一个可调用对象(函数适配器)。 - 接着,适配器和指定的启动策略被传递给
_Get_associated_state函数,目的是获取与异步操作相关的状态。 - 最终,
_Get_associated_state返回一个与异步操作相关的状态,并将其传递给_Pr,这将会返回一个_Promise<_Ptype>,代表一个异步操作的结果。
_Get_associated_state函数根据启动模式(_Policy,有三种)来决定创建的异步任务状态对象类型:template <class _Ret, class _Fty> _Associated_state<typename _P_arg_type<_Ret>::type>* _Get_associated_state(launch _Psync, _Fty&& _Fnarg) {// construct associated asynchronous state object for the launch typeswitch (_Psync) { // select launch typecase launch::deferred:return new _Deferred_async_state<_Ret>(_STD forward<_Fty>(_Fnarg));case launch::async: // TRANSITION, fixed in vMajorNext, should create a new thread heredefault:return new _Task_async_state<_Ret>(_STD forward<_Fty>(_Fnarg));} }_Get_associated_state函数返回一个_Associated_state指针(_Associated_state可用于初始化_State_manager,_State_manager可用于初始化_Promise),该指针指向一个新的_Deferred_async_state或_Task_async_state对象。这两个类分别对应于异步任务的两种不同执行策略:延迟执行和异步执行。这段代码也很好的说明,
launch::async | launch::deferred和launch::async的行为是相同的,都会创建新线程异步执行任务,只不过前者会自行判断系统资源来抉择。
_Task_async_state与_Deferred_async_state都继承自_Packaged_state,其用于异步执行任务。它们的构造函数都接受一个函数对象,并将其转发给基类_Packaged_state的构造函数。// _Task_async_state 的构造函数 template <class _Rx> class _Task_async_state : public _Packaged_state<_Rx()> // _Deferred_async_state 的构造函数 template <class _Rx> class _Deferred_async_state : public _Packaged_state<_Rx()>_Packaged_state类型只有一个数据成员 :std::function类型的对象_Fn,它用来存储需要执行的异步任务,而它又继承自_Associated_state。template <class _Ret, class... _ArgTypes> class _Packaged_state<_Ret(_ArgTypes...)>: public _Associated_state<_Ret>
如上图所示,
_Task_async_state与_Deferred_async_state都继承自_Packaged_state,_Packaged_state中保存了传入给std::async的可调用对象。同时,_Packaged_state继承自_Associated_state,_Associated_state是_Primise类中成员_State的最基本组成对象,基本所有的接口都是通过调用_Associated_state的函数实现的。_Task_async_state与_Deferred_async_state的构造函数如下:// _Task_async_state template <class _Fty2> _Task_async_state(_Fty2&& _Fnarg) : _Mybase(_STD forward<_Fty2>(_Fnarg)) {_Task = ::Concurrency::create_task([this]() { // do it nowthis->_Call_immediate();});this->_Running = true; } // _Deferred_async_state template <class _Fty2> _Deferred_async_state(const _Fty2& _Fnarg) : _Packaged_state<_Rx()>(_Fnarg) {} template <class _Fty2> _Deferred_async_state(_Fty2&& _Fnarg) : _Packaged_state<_Rx()>(_STD forward<_Fty2>(_Fnarg)) {}a. _Task_async_state
_Task_async_state有一个数据成员_Task用于从线程池中获取线程,并执行可调用对象:private:::Concurrency::task<void> _Task;_Task_async_state的实现使用了微软实现的并行模式库(PPL)。简而言之,launch::async策略并不是单纯的创建线程让任务执行,而是使用了微软的::Concurrency::create_task,它从线程池中获取线程并执行任务返回包装对象。this->_Call_immediate()是调用_Task_async_state的父类_Packaged_state的成员函数_Call_immediate._Packaged_state有三个版本,自然_Call_immediate也有三种版本,用于处理可调用对象返回类型的三种情况:// 返回普通类型 // class _Packaged_state<void(_ArgTypes...)> void _Call_immediate(_ArgTypes... _Args) {_TRY_BEGIN// 调用函数对象并捕获异常 传递返回值this->_Set_value(_Fn(_STD forward<_ArgTypes>(_Args)...), false);_CATCH_ALL// 函数对象抛出异常就记录this->_Set_exception(_STD current_exception(), false);_CATCH_END }// 返回引用类型 // class _Packaged_state<_Ret&(_ArgTypes...)> void _Call_immediate(_ArgTypes... _Args) {_TRY_BEGIN// 调用函数对象并捕获异常 传递返回值的地址this->_Set_value(_STD addressof(_Fn(_STD forward<_ArgTypes>(_Args)...)), false);_CATCH_ALL// 函数对象抛出异常就记录this->_Set_exception(_STD current_exception(), false);_CATCH_END }// 返回void类型 // class _Packaged_state<void(_ArgTypes...)> void _Call_immediate(_ArgTypes... _Args) { _TRY_BEGIN// 调用函数对象并捕获异常 因为返回 void 不获取返回值 而是直接 _Set_value 传递一个 1_Fn(_STD forward<_ArgTypes>(_Args)...);this->_Set_value(1, false);_CATCH_ALL// 函数对象抛出异常就记录this->_Set_exception(_STD current_exception(), false);_CATCH_END }_Fn(_STD forward<_ArgTypes>(_Args)...表示执行可调用对象,this->_Set_value(_Fn(_STD forward<_ArgTypes>(_Args)...)表示将可调用对象的返回值传入给_Set_value,其他两个函数也是类似的处理过程。_TRY_BEGIN、_CATCH_ALL、_CATCH_END类似try-catch块。当_Fn函数对象抛出异常时,控制流会跳转到_CATCH_ALL代码块;this->_Set_exception用来记录当前捕获的异常;_CATCH_END标识异常处理的结束;因为返回类型为void表示不获取返回值,所以这里通过_Set_value传递一个 1(表示正确执行的状态)。所有的返回值均传入给_Set_value。简而言之,就是把返回引用类型的可调用对象返回值的引用获取地址传递给
_Set_value,把返回 void 类型的可调用对象传递一个 1 (表示正确执行的状态)给_Set_value。_Set_value、_set_exception函数来自_Packaged_state模板类的父类_Associated_state,通过这两个函数,传递的可调用对象执行结果,以及可能的异常,并将结果或异常存储在_Associated_state中。b. _Deferred_async_state
_Deferred_async_state并不会从线程池中获取一个新线程,然后再新线程中执行任务,而是当前线程调用future的get或者wait函数时,在当前线程中同步执行。但它同样调用_Call_immediate函数执行存储的可调用对象,它有一个_Run_deferred_function函数:void _Run_deferred_function(unique_lock<mutex>& _Lock) override { // run the deferred function_Lock.unlock();_Packaged_state<_Rx()>::_Call_immediate();_Lock.lock(); }然后通过
_Call_immediate调用可调用对象并通过函数_Set_value、_set_exception存储可调用对象返回结果或者异常至_Associated_state。 - 首先将参数
-
返回
std::futurereturn future<_Ret>(_Pr._Get_state_for_future(), _Nil());_Ret在前面说了,其实就是可调用对象返回值的类型。传给
future构造函数的参数之一是:_Pr._Get_state_for_future(),调用上面构造的_Promise的成员函数_Get_state_for_future,该函数用于返回_Promise类的私有成员变量_State。_Get_state_for_future函数的实现如下:_State_manager<_Ty>& _Get_state_for_future() {if (!_State.valid()) {_Throw_future_error2(future_errc::no_state);}if (_Future_retrieved) {_Throw_future_error2(future_errc::future_already_retrieved);}_Future_retrieved = true;return _State; }其实就是调用
_State的成员函数valid()检查状态(是否有错),然后判断future是否提前返回可调用对象的返回值(如果是,代表future的get被调用,抛出异常);最后,返回_State。
2. std::future
我们首先从一个最简单的std::async示例开始:
std::future<int> future = std::async([] { return 0; });
future.get();
我们从之前的学习中了解到,future.get()就是从future中获取可调用对象的返回结果。唯一的问题是:future.get() 内部执行了什么流程?首先从future的实现开始:
_EXPORT_STD template <class _Ty>
class future : public _State_manager<_Ty> {// class that defines a non-copyable asynchronous return object that holds a value
private:using _Mybase = _State_manager<_Ty>;public:static_assert(!is_array_v<_Ty> && is_object_v<_Ty> && is_destructible_v<_Ty>,"T in future<T> must meet the Cpp17Destructible requirements (N4950 [futures.unique.future]/4).");future() = default;future(future&& _Other) noexcept : _Mybase(_STD move(_Other), true) {}future& operator=(future&&) = default;future(_From_raw_state_tag, const _Mybase& _State) noexcept : _Mybase(_State, true) {}_Ty get() {// block until ready then return the stored result or throw the stored exceptionfuture _Local{_STD move(*this)};return _STD move(_Local._Get_value());}_NODISCARD shared_future<_Ty> share() noexcept {return shared_future<_Ty>(_STD move(*this));}future(const future&) = delete;future& operator=(const future&) = delete;
};
future类继承自_State_manager类,_State_manager类又有一个_Associated_state<_Ty>*类型的私有成员_State,而_State_manager的接口实现大部分是通过调用_Associated_state 的成员函数实现的。关系如下:

2.1 wait()
但你可能发现一个问题,
future类怎么没有wait()成员函数????其实,wait()函数继承自父类_State_manager。
void wait() const { // wait for signalif (!valid()) {_Throw_future_error2(future_errc::no_state);}_Assoc_state->_Wait();
}
而_State_manager类的wait()其实是通过调用_Associated_state的接口实现的,所以说,_Associated_state在std::async和std::future中是非常核心的。
virtual void _Wait() { // wait for signalunique_lock<mutex> _Lock(_Mtx);_Maybe_run_deferred_function(_Lock);while (!_Ready) {_Cond.wait(_Lock);}
}
_Associated_state的wait()函数通过unique_lock保护共享数据,然后调用_Maybe_run_deferred_function执行可调用对象,直至调用结束。
void _Maybe_run_deferred_function(unique_lock<mutex>& _Lock) { // run a deferred function if not already doneif (!_Running) { // run the function_Running = true;_Run_deferred_function(_Lock);}
}
_Maybe_run_deferred_function其实就是通过调用_Run_deferred_function来调用_Call_immediate(),我们在async源码中学习过_Run_deferred_function和`_Call_immediate()。
void _Run_deferred_function(unique_lock<mutex>& _Lock) override { // run the deferred function_Lock.unlock();_Packaged_state<_Rx()>::_Call_immediate();_Lock.lock();
}
在 _Wait 函数中调用 _Maybe_run_deferred_function 是为了确保延迟执行(launch::deferred)的任务能够在等待前被启动并执行完毕。这样,在调用 wait 时可以正确地等待任务完成。
因为只有std::launch::deferred才是当调用future.get或者wait时才会执行_Call_immediate(),其他两种启动策略在大部分情况下都是直接执行,通过future.get获得结果。所以我们必须保证在调用wait函数时,执行std::launch::deferred策略的任务被执行,而其他两种启动策略早已经执行任务,无需再调用_Call_immediate()。所以在_Maybe_run_deferred_function函数中,有下面一段,判断任务是否以及执行,如果被执行,那么久就不调用_Call_immediate,反之调用。
if (!_Running) { // run the function_Running = true;_Run_deferred_function(_Lock);
while (!_Ready) {_Cond.wait(_Lock);}
通过条件变量挂起当前线程,等待可调用对象执行完毕。在等待期间,当前线程释放持有的锁,保证其他线程再次期间可以访问到共享资源,待当前线程被唤醒后,重新持有锁。其主要作用是:
- 避免虚假唤醒:
- 条件变量的
wait函数在被唤醒后,会重新检查条件(即_Ready是否为true),确保只有在条件满足时才会继续执行。这防止了由于虚假唤醒导致的错误行为。
- 条件变量的
- 等待
launch::async的任务在其它线程执行完毕:- 对于
launch::async模式的任务,这段代码确保当前线程会等待任务在另一个线程中执行完毕,并接收到任务完成的信号。只有当任务完成并设置_Ready为true后,条件变量才会被通知,从而结束等待。
- 对于
这样,当调用 wait 函数时,可以保证无论任务是 launch::deferred 还是 launch::async 模式,当前线程都会正确地等待任务的完成信号,然后继续执行。
std::future 其实还有两种特化,不过整体大差不差。
template <class _Ty>
class future<_Ty&> : public _State_manager<_Ty*>
template <>
class future<void> : public _State_manager<int>
也就是对返回类型为引用和 void 的情况了。其实先前已经聊过很多次了,无非就是内部的返回引用实际按指针操作,返回 void,那么也得给个 1,表示正常运行的状态。类似于前面 _Call_immediate 的实现。
2.2 get()
get()函数是future的成员函数,而没有继承父类_State_manager:
// std::future<void>
void get() {// block until ready then return or throw the stored exceptionfuture _Local{_STD move(*this)};_Local._Get_value();
}
// std::future<T>
_Ty get() {// block until ready then return the stored result or throw the stored exceptionfuture _Local{_STD move(*this)};return _STD move(_Local._Get_value());
}
// std::future<T&>
_Ty& get() {// block until ready then return the stored result or throw the stored exceptionfuture _Local{_STD move(*this)};return *_Local._Get_value();
}
因为future有三种特化,所以get()函数也有三种特化。它们将当前future对象的指针通过std::move转移给类型为future的局部变量_Local(转移后,原本的future对象便失去了所有权)。然后,局部变量_Local调用成员函数_Get_value(),并将结果返回。
注意:局部对象
_Local在函数结束时析构。这意味着当前对象(*this)失去共享状态,并且状态被完全销毁。
_Get_value() 函数的实现如下:
_Ty& _Get_value() const {if (!valid()) {_Throw_future_error2(future_errc::no_state);}return _Assoc_state->_Get_value(_Get_only_once);
}
future.valid() 成员函数检查 future 当前是否关联共享状态,即是否当前关联任务。如果还未关联,或者任务已经执行完(调用了 get()、set()),都会返回 false。
- 首先,通过
valid()判断当前future对象是否关联共享状态,如果没,抛出异常。 - 最后,调用
_Assoc_state的成员函数_Get_value,传递_Get_only_once参数,其实就是代表这个成员函数只能调用一次。
_Assoc_state 的类型是 _Associated_state<_Ty>* ,是一个指针类型,它实际会指向自己的子类对象,我们在讲 std::async 源码的时候提到了,它必然指向 _Deferred_async_state 或者 _Task_async_state。
_Assoc_state->_Get_value 这其实是个多态调用,父类有这个虚函数:
virtual _Ty& _Get_value(bool _Get_only_once) {unique_lock<mutex> _Lock(_Mtx);if (_Get_only_once && _Retrieved) {_Throw_future_error2(future_errc::future_already_retrieved);}if (_Exception) {_STD rethrow_exception(_Exception);}// TRANSITION: `_Retrieved` should be assigned before `_Exception` is thrown so that a `future::get`// that throws a stored exception invalidates the future (N4950 [futures.unique.future]/17)_Retrieved = true;_Maybe_run_deferred_function(_Lock);while (!_Ready) {_Cond.wait(_Lock);}if (_Exception) {_STD rethrow_exception(_Exception);}if constexpr (is_default_constructible_v<_Ty>) {return _Result;} else {return _Result._Held_value;}
}
子类 _Task_async_state 对其进行了重写,以 launch::async 策略或者std::launch::async | std::launch::deferred策略创建的future,实际会调用 _Task_async_state::_Get_value :
_State_type& _Get_value(bool _Get_only_once) override {// return the stored result or throw stored exception_Task.wait();return _Mybase::_Get_value(_Get_only_once);
}
_Deferred_async_state没有对其进行重写,直接调用父类虚函数。
_Task 就是 ::Concurrency::task<void> _Task;,调用 wait() 成员函数确保任务执行完毕。
_Mybase::_Get_value(_Get_only_once) 其实又是回去调用父类的虚函数了。
_Get_value方法详解
-
_Get_value()只能调用一次
如果
_Get_only_once为true且_Retrieved为true(表示结果已经被检索过),则抛出future_already_retrieved错误。 -
处理异常
如果在获取结果过程中出现了异常,需要重新抛出该异常
-
设置
_Retrieved为true在获取值之前,设置
_Retrieved为true,表示结果已经被检索过。这样可以确保future对象不会被重复获取,避免多次调用get时引发错误。 -
执行延迟函数
调用
_Maybe_run_deferred_function来运行可能的延迟任务。在该函数内部,如果任务已经运行,那么退出,如果没运行,调用_Call_immediate()函数执行可调用对象。 -
等待结果
使用条件变量挂起当前线程,确保线程同步,即只有当异步任务准备好返回结果时,线程才会继续执行。
-
再次检查异常
线程被唤醒将结果存储至
future对象中后,再次判断是否发生了异常,需要重新抛出异常 -
返回结果
这部分代码根据
_Ty类型的特性决定如何返回结果:- 如果
_Ty类型是 默认可构造(即_Ty的默认构造函数有效),直接返回_Result。 - 否则,返回
_Result._Held_value
is_default_constructible_v<_Ty>是一个C++17引入的类型特征,用于检查类型_Ty是否具有默认构造函数。_Result是future中持有的结果,而_Held_value是存储在_Result中的实际值。 - 如果
_Result 是通过执行 _Call_immediate 函数,然后 _Call_immediate 再执行 _Set_value ,_Set_value 再执行 _Set_value_raw,_Set_value_raw再执行_Emplace_result并通知线程可以醒来,_Emplace_result 获取到我们执行任务的返回值的。以 Ty 的偏特化为例:
// _Packaged_state
void _Call_immediate(_ArgTypes... _Args) {_TRY_BEGIN// 调用函数对象并捕获异常 传递返回值this->_Set_value(_Fn(_STD forward<_ArgTypes>(_Args)...), false);_CATCH_ALL// 函数对象抛出异常就记录this->_Set_exception(_STD current_exception(), false);_CATCH_END
}// _Asscoiated_state
void _Set_value(const _Ty& _Val, bool _At_thread_exit) { // store a resultunique_lock<mutex> _Lock(_Mtx);_Set_value_raw(_Val, &_Lock, _At_thread_exit);
}
void _Set_value_raw(const _Ty& _Val, unique_lock<mutex>* _Lock, bool _At_thread_exit) {// store a result while inside a locked blockif (_Already_has_stored_result()) {_Throw_future_error2(future_errc::promise_already_satisfied);}_Emplace_result(_Val);_Do_notify(_Lock, _At_thread_exit);
}
template <class _Ty2>
void _Emplace_result(_Ty2&& _Val) {// TRANSITION, incorrectly assigns _Result when _Ty is default constructibleif constexpr (is_default_constructible_v<_Ty>) {_Result = _STD forward<_Ty2>(_Val); // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!} else {::new (static_cast<void*>(_STD addressof(_Result._Held_value))) _Ty(_STD forward<_Ty2>(_Val));_Has_stored_result = true;}
}
相关文章:
并发编程(8)—— std::async、std::future 源码解析
文章目录 八、day81. std::async2. std::future2.1 wait()2.2 get() 八、day8 之前说过,std::async内部的处理逻辑和std::thread相似,而且std::async和std::future有密不可分的联系。今天,通过对std::async和std::future源码进行解析&#x…...
稻米分类和病害检测数据集(猫脸码客 第237期)
稻米分类图像数据集:推动农业智能化发展的关键资源 在农业领域,稻米作为世界上最重要的粮食作物之一,其品种繁多,各具特色。然而,传统的稻米分类方法往往依赖于人工观察和经验判断,不仅耗时费力࿰…...
HANDLINK ISS-7000v2 网关 login_handler.cgi 未授权RCE漏洞复现
0x01 产品简介 瀚霖科技股份有限公司ISS-7000 v2网络网关服务器是台高性能的网关,提供各类酒店网络认证计费的完整解决方案。由于智慧手机与平板电脑日渐普及,人们工作之时开始使用随身携带的设备,因此无线网络也成为网络使用者基本服务的项目。ISS-7000 v2可登录300至1000…...
基于Multisim串联型连续可调直流稳压正电源电路设计与仿真
设计任务和要求: (1)输出直流电压 1.5∽10V 可调; (2)输出电流 IOm300mA;(有电流扩展功能) (3)稳压系数 Sr≤0.05; (4&…...
【QT】Qt文件和多线程
个人主页~ Qt系统内容 一、Qt文件1、文件读写读写 2、文件和目录信息 二、多线程1、线程使用timethread.hwidget.htimethread.cppwidget.cpp 2、线程安全(1)互斥锁QMutexQMutexLocker一个例子mythread.hmythread.cppwidget.cpp QReadWriteLocker、QReadL…...
PN结如何实现不同反向耐压及达到高反向耐压
目录 1. PN结实现不同耐压值 2. PN如何达到高反向耐压 1. PN结实现不同耐压值 主要通过以下几个方面: • PN结设计:不同耐压值的二极管在PN结的设计上有所不同。通过调整PN结的宽度和深度,可以改变空间电荷区的大小,从而影响二极…...
【bug日志-水】解决本地开发下代理和url同名导致刷新404的问题
bug描述 在本地开发,并且路由是history的模式下,代理和url同名的情况下,刷新会404。 {path: /googleAds,//如果有个代理也叫googleAds,刷新时就会404name: googleAds,icon: sound,routes: [{path: /googleAds/GoogleAdsSettingPag…...
Hive面试题-- 查询各类型专利 top10 申请人及专利申请数
在数据处理中,尤其是涉及到专利信息等复杂数据时,Hive 是一个强大的工具。本文将详细介绍如何使用 Hive 查询语句来获取各类型专利 top10 申请人以及他们对应的专利申请数,以下是基于给定的 t_patent_detail 表结构的分析和查询步骤。 建表语…...
996引擎 - 活捉NPC
996引擎 - 活捉NPC 引擎触发 - 引擎事件(QF)事件处理模块 GameEvent测试文件参考资料 引擎触发 - 引擎事件(QF) cfg_game_data 配置 ShareNpc1 可以将QM和机器人的触发事件全部转到 QF 引擎触发是通用的,TXT的所有触发转换成小写后在LUA中就可使用,如说明书中缺省可反馈至对接群…...
航展畅想:从F35机载软件研发来看汽车车载软件研发
两款经典战机的机载软件 F-22和F-35战斗机的研制分别始于1980年代和1990年代末,F-22项目在1981年启动,主要由洛克希德马丁(Lockheed Martin)和波音公司(Boeing)合作开发,以满足美军“先进战术战…...
用Dify搭建AI知识库
Dify 可以上传各种格式文档和抓取网页数据训练自已的知识库 一 安装 1 Docker安装 我基于Docker来安装的,所以本机先装Docker Desktop, Docker 安装方法在这里 2 Dify 安装 git clone https://github.com/langgenius/dify.git cd dify/docker copy .env.exampl…...
架构师:如何提高web网站的请求并发响应量?
文章目录 一、提出问题二、相关概念三、如何提高网站请求响应能力?四、负载均衡有那些方式?五、常用微服务架构图及推荐书籍 一、提出问题 今天,突然想到一个问题,双十一,那些电商网站的并发量是多大? 简…...
图论基础--孤岛系列
孤岛系列有: 孤岛总面积求解(用了dfs、bfs两种方法)和沉没孤岛(这里只写了dfs一种) 简单解释一下: 题目中孤岛的定义是与边缘没有任何接触的(也就是不和二维数组的最外圈连接)&…...
Docker学习—Docker的安装与使用
Docker安装 1.卸载旧版 首先如果系统中已经存在旧的Docker,则先卸载: yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine2.配置Docker的yum库 首先…...
HC-SR04超声波传感器详解(STM32)
HC-SR04是一款广泛使用的超声波传感器,它通过发射和接收超声波来测量距离。本文将详细介绍HC-SR04的工作原理、引脚描述、STM32的接线方式以及如何通过STM32控制HC-SR04来测量距离。 一、HC-SR04传感器介绍 HC-SR04超声波传感器的主要参数如下: 工作电…...
如何在BSV区块链上实现可验证AI
发表时间:2024年10月2日 nChain的顶尖专家们已经找到并成功测试了一种方法:通过区块链技术来验证AI(人工智能)系统的输出结果。这种方法可以确保AI模型既按照规范运行,避免严重错误,遵守诸如公平、透明…...
Python快速安装软件包到环境的方案
问题描述 直接在终端输入,显示安装numpy包要20分钟, pip install numpyxxx.whl解决方案 直接搜索pip install 后在终端显示的.whl文件,在pypi.org官网下载, 之后在终端进入下载目录,从.whl文件安装软件包即可 pip …...
npm入门教程17:准备发布的npm包
一、环境准备 安装Node.js和npm: 确保你的计算机上已安装Node.js和npm。可以通过运行node -v和npm -v命令来检查它们的版本。如果没有安装,可以从Node.js官方网站下载并安装最新版本。 注册npm账号: 访问npm官网,点击“Sign Up”…...
协程1 --- 发展历史
文章目录 一个编译器问题背景解决 协程为什么一开始没发展成一等公民?自顶向下、逐步求精(Top-down, stepwise refinement)线程的出现 协程的雄起IO密集型同步语义实现异步发展史 线程和协程的关系并发性调度方式资源占用 一个编译器问题 协…...
VBA10-处理Excel的动态数据区域
end获取数据边界 1、基本语法 1-1、示例: 2、配合row和column使用 2-1、示例1 2-2、示例2 此时,不管这个有数值的区域,怎么增加边界,对应的统计数据也会跟着变的!...
(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)
题目:3442. 奇偶频次间的最大差值 I 思路 :哈希,时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况,哈希表这里用数组即可实现。 C版本: class Solution { public:int maxDifference(string s) {int a[26]…...
装饰模式(Decorator Pattern)重构java邮件发奖系统实战
前言 现在我们有个如下的需求,设计一个邮件发奖的小系统, 需求 1.数据验证 → 2. 敏感信息加密 → 3. 日志记录 → 4. 实际发送邮件 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其…...
多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
PHP和Node.js哪个更爽?
先说结论,rust完胜。 php:laravel,swoole,webman,最开始在苏宁的时候写了几年php,当时觉得php真的是世界上最好的语言,因为当初活在舒适圈里,不愿意跳出来,就好比当初活在…...
AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...
高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...
现代密码学 | 椭圆曲线密码学—附py代码
Elliptic Curve Cryptography 椭圆曲线密码学(ECC)是一种基于有限域上椭圆曲线数学特性的公钥加密技术。其核心原理涉及椭圆曲线的代数性质、离散对数问题以及有限域上的运算。 椭圆曲线密码学是多种数字签名算法的基础,例如椭圆曲线数字签…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
