C++异步调用利器future/promise实现原理

2023-05-16

前言

在异步编程中,各种回调将让人眼花缭乱,代码分散,维护起来十分困难。boost和C++11 的 future/promise 提供了一个很好的解决方案,使得代码更加漂亮、易维护。

在工作中,我也用过几次future/promise,但是还是十分生疏,所以决定学习下它的原理,用起来才更加顺畅。

查了很多资料,发现很多语言都有这个机制,但是关于C++的promise的资料却很少,只有一些使用的教程,而没有找到原理方面的。

“源码之前,了无秘密。”

所以还是决定从源码入手学习!

本文针对的源码是借鉴boost实现的版本,由于copyright的原因,就不贴出完整的源码了,需要学习的朋友可以参见boost的实现。

关于future/promise的简介,参见我之前写的一篇博文:

http://blog.csdn.net/jiange_zh/article/details/51602938

一、基于Future/Promise的异步同步化编程依赖的组件

  1. bind和callback,类似与boost库的bind和function;
  2. shared_ptr、scoped_ptr、tuple、exception,参考boost库中的shared_ptr、scoped_ptr、tuple、exception实现;
  3. Future和Promise,借鉴boost库的future设计思想;
  4. when_all,通过Future、Promise、tuple来实现,针对异步并行的同步化。

二、Function和Bind的用法

参见我之前写的一篇博文:

http://blog.csdn.net/jiange_zh/article/details/51598580

下面提到的bind类似于boost的bind,而Callback类似于Function,一般跟bind搭配使用。

三、shared_ptr、scoped_ptr、tuple

shared_ptr:引用计数。

scoped_ptr:不可转移所有权。

Tuple:很多的时候我们需要为函数返回多个值,我们可以使用class或struct来封装要返回的多个值,然后返回封装struct或class,但是使用这种方法的弊端就是增加的程序的代码量,最好是能通过一种匿名的struct或class来解决这个问题。Boost::tuple就为我们提供了一种类似于匿名struct的方法为我们解决函数的多个返回值的问题。既增强了代码的可读性又不增加代码量。其实std::pair就是boost::tuple的2个参数的特例,对boost::tuple你可以绑定更多的参数,或者你可以迭代实现无限多参数的情况。

四、EnableSharedFromThis

使用boost库时,经常会看到如下的类:

class A:public enable_share_from_this<A>

在什么情况下要使类A继承enable_share_from_this?

使用场合 :当类A被share_ptr管理,且在类A的成员函数里需要把当前类对象作为参数传给其他函数时,就需要传递一个指向自身的share_ptr。

我们就使类A继承enable_share_from_this,然后通过其成员函数 share_from_this()返回当指向自身的share_ptr。

以上有2个疑惑:

1.把当前类对象作为参数传给其他函数时,为什么要传递share_ptr呢?直接传递this指针不可以吗?

一个裸指针传递给调用者,谁也不知道调用者会干什么?假如调用者delete了该对象,而share_tr此时还指向该对象。

2.这样传递share_ptr可以吗?share_ptr< this >

这样会造成2个非共享的share_ptr指向一个对象,最后造成2次析构该对象。

boost官方文档中一个非常典型的例子:

http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/tutorial/tutdaytime3/src.html

部分代码:

 1 class tcp_connection
 2   : public boost::enable_shared_from_this<tcp_connection>
 3 {
 4 public:
 5   typedef boost::shared_ptr<tcp_connection> pointer;
 6 
 7   static pointer create(boost::asio::io_service& io_service)
 8   {
 9     return pointer(new tcp_connection(io_service));
10   }
11 
12   tcp::socket& socket()
13   {
14     return socket_;
15   }
16 
17   void start()
18   {
19     message_ = make_daytime_string();
20 
21     boost::asio::async_write(socket_, boost::asio::buffer(message_),
22         boost::bind(&tcp_connection::handle_write, shared_from_this(),
23           boost::asio::placeholders::error,
24           boost::asio::placeholders::bytes_transferred));
25   }
26 
27 private:
28   tcp_connection(boost::asio::io_service& io_service)
29     : socket_(io_service)
30   {
31   }
32 
33   void handle_write(const boost::system::error_code& /*error*/,
34       size_t /*bytes_transferred*/)
35   {
36   }
37 
38   tcp::socket socket_;
39   std::string message_;
40 };

类tcp_connection继承enable_share_from_this,在22行里,它的成员函数start(),通过share_from_this返回指向自身的share_ptr。

五、Future和Promise

5.1 简介

Promise对象可保存T类型的值,该值可被future对象读取(可能在另一个线程中),这是promise提供的同步的一种手段。

在构造promise时,promise对象可以与共享状态关联起来,这个共享状态可以存储一个T类型或者一个由std::exception派生出的类的值,并可以通过get_future来获取与promise对象关联的对象,调用该函数之后,两个对象共享相同的共享状态(shared state)

Promise对象是异步provider,它可以在某一时刻设置共享状态的值。

Future对象可以返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标识变为ready,然后才能获取共享状态的值。

5.2 关系图总览

这里写图片描述

5.3从使用入手学习

下面是使用future/promise的一个例子(目前仅讨论串行调用的情况):

//服务对外接口,串行调用
taf::Int32 
AServantImp::queryResultSerial(const std::string& sIn, std::string &sOut, taf::JceCurrentPtr current)
{
    //设置异步回包
    current->setResponse(false);

    // 向服务B发送异步请求,返回值的类型是
    // promise::Future<std::string>,
    // 意思就是服务B未来会返回一个string类型的数据
    promise::Future<std::string> f = sendBReq(_pPrxB, sIn, current);

    // f调用其成员函数then,给未来要到达的string类型的
    // 返回结果设置一个处理函数
    // 在handleBRspAndSendCReq中获取返回结果,
    // 并return sendCReq(),即f2,然后f2通过链式法则调用then
    f.then(promise::bind(&handleBRspAndSendCReq,_pPrxC,current))
    .then(promise::bind(&handleCRspAndReturnClient, current));

    return 0;
}

promise::Future<std::string> 
sendBReq(BServantPrx prx, const std::string& sIn, taf::JceCurrentPtr current)
{
    // 定义一个promise::Promise<std::string>类型的变量promise,
    // 其目的是承诺会在promise里面存放一个string类型的数据,
    // 然后把这个变量传到BServantCallback对象中,
    // 然后发起异步调用
    // 最后返回promise.getFuture(),
    // 意思是promise承诺的string类型数据
    // 可以通过promise::Future<std::string>类型的
    // promise.getFuture()来获得

    promise::Promise<std::string> promise;

    Test::BServantPrxCallbackPtr cb = new BServantCallback(current, promise);

    prx->async_queryResult(cb, sIn);

    return promise.getFuture();     //返回一个future给f
}

//////////////////////////////////////////////////////
promise::Future<std::string> 
sendCReq(CServantPrx prx, const std::string& sIn, taf::JceCurrentPtr current)
{
    //这个跟sendBReq的意思类似
    //……
}

//////////////////////////////////////////////////////
promise::Future<std::string> 
handleBRspAndSendCReq(CServantPrx prx, JceCurrentPtr current, const promise::Future<std::string>& future)
{
    std::string sResult("");
    std::string sException("");
    try
    {
        //此行代码被执行的时候,promie承诺给future的数据已经达到
        //达到的数据分两种情况,一是正常的数据,即请求服务B的结果数据返回过来了,
        //那么调用future.get()会得到这个数据
        //二是异常的数据,即请求服务B的结果数据没有返回,比如异步调用超时了
        //那么调用future.get()会抛出异常,所以需要try-catch一下

        sResult = future.get();
        return sendCReq(prx, sResult, current);
    }
    catch (exception& e)
    {
        TLOGDEBUG("Exception:" << e.what() << endl);
        sException = e.what();
    } 

    promise::Promise<std::string> promise;
    promise.setValue(sException); 
    return promise.getFuture();
}

//////////////////////////////////////////////////////
int 
handleCRspAndReturnClient(JceCurrentPtr current, const promise::Future<std::string>& future)
{
    int ret = 0;
    std::string sResult("");

    try
    {
        //与handleBRspAndSendCReq处理类似
        sResult = future.get();
    }
    catch (exception& e)
    {
        ret = -1;
        sResult = e.what();
        TLOGDEBUG("Exception:" << e.what() << endl);
    }

    //回包
    AServant::async_response_queryResultSerial(current, ret, sResult);
    return 0;
}

我们一步步看看发生了什么吧~

5.4定义并初始化一个Future类型的变量

promise::Future<std::string> f = sendBReq(_pPrxB, sIn, current); 

sendBReq通过promise.getFuture()返回了一个Future,用它来初始化f。

两个问题:
1.promise.getFuture()是怎么来的?
2.f是如何初始化的?

1.promise.getFuture()是怎么来的?

promise::Promise<std::string> promise;
Test::BServantPrxCallbackPtr cb = new BServantCallback(current, promise);
prx->async_queryResult(cb, sIn);
return promise.getFuture();     //返回一个future给f

promise内部有一个数据成员:

SharedPtr<detail::FutureObjectInterface<T> > m_future; 

该成员的默认构造:

Promise()
        : m_future(SharedPtr<detail::FutureObject<T> >(new detail::FutureObject<T>()))
    {}

它使用了FutureObject来作为FutureObjectInterface的具体实现。

Promise的getFuture()方法用m_future构造了一个临时对象Future< T >(该构造函数为private,因此需要Future将Promise设为友元)并返回,因此promise.getFuture()临时对象中的m_future和promise中的m_future指向同一个FutureObject对象。

Future<T> getFuture() const
{
    return Future<T> (m_future);
}

2.f是如何初始化的?

Future< T >继承自FutureBase< T >,继承的数据成员:

typedef SharedPtr<detail::FutureObjectInterface<T> > FuturePtr;
FuturePtr m_future;

我们的目的就是用promise.getFuture()(一个匿名的临时对象)的m_future来初始化f的m_future,使promise、promise.getFuture()和f的m_future均指向同一个对象,之后promise.getFuture()临时对象析构,只剩下promise和f的m_future指向同一个对象,有了这个共享,我们就可以在promise中进行赋值(在BServantCallback中调用setValue进行赋值),而在f中进行读取(通过f.get())!

Future< T >的3个public构造函数:

Future() {}

explicit Future(typename detail::FutureTraits<T>::rvalue_source_type t)
: detail::FutureBase<T>( SharedPtr<detail::PromptFutureObject<T> >
(new detail::PromptFutureObject<T>(t)) )
    {}

Future(const ExceptionPtr& e)
        : detail::FutureBase<T>(e)
{}

对于第二个,由于T为string,detail::FutureTraits< T >::rvalue_source_type实际上就是 const std::string&。

从上面看,并没有匹配的构造函数可用,且其父类也没有拷贝构造函数,因此编译器会进行成员逐个拷贝,最终将sendBReq中的m_future成员拷贝过来,该成员由shared_ptr进行管理,因此promise和f的m_future指向同一个对象的目的达到。

5.5绑定回调函数进行处理,处理完毕之后链式调用

当promise承诺的值设置好之后,需要回调函数进行处理。因此我们需要通过then来绑定回调函数。另外,为支持链式调用,then应该返回一个future,这个future一般是回调函数的返回值,在回调函数中通过promise.getFuture()来获取。

Future< T >从FutureBase< T >继承下来的成员函数:

get();
isDone();
hasValue();
hasException();
operator unspecified_bool_type() const; // Returns true if this future has been initialized.

以上函数都是转调用m_future的相关函数,因此整个future的细节封装在了FutureObject和PromptFutureObject当中,这里先不深究。

Future< T >另外自己实现了一个函数then:

/**
* Register a callback which will be called once the future is satisfied. If an
* exception is thrown the callback will not be registered and then will not be called.
* 
* \throws std::bad_alloc if memory is unavailable.
*/
template <typename R>
Future<typename detail::resolved_type<R>::type> 
then(const Callback<R(const Future&)>& callback) const
{
    typedef typename detail::resolved_type<R>::type value_type;

    if (!this->m_future)
    {
        throwException(FutureUninitializedException(__FILE__, __LINE__));
    }

    Promise<value_type> promise;

    this->m_future->registerCallback(
        bind(&detail::SequentialCallback<R, T>::template run<R>,
            owned(new detail::SequentialCallback<R, T>(callback, promise))));

    return promise.getFuture();
}

该函数接受一个Callback对象作为参数,Callback封装了一个函数,其返回值为R,参数为Future。

比如以下调用:

f.then(promise::bind(&handleBRspAndSendCReq,_pPrxC,current));

其中handleBRspAndSendCReq的签名如下:

promise::Future<std::string> handleBRspAndSendCReq(CServantPrx prx, JceCurrentPtr current, const promise::Future<std::string>& future);

bind绑定了函数handleBRspAndSendCReq的前两个参数prx和current,剩下第三个参数future。

5.5.1 then的返回值类型

看完参数,我们来看看then的返回值类型:

Future<typename detail::resolved_type<R>::type> 

在上面例子中,handleBRspAndSendCReq的返回类型为promise::Future< std::string >,它被用来具现化then的模板参数R。

为什么这里的返回值类型不直接使用R,而要通过resolved_type来决议呢?

我们先看一下resolved_type的定义:

    template <typename T>
    struct resolved_type 
    {
        typedef T type;
    };

    template <typename T>
    struct resolved_type<Future<T> > 
    {
        typedef T type;
};

resolved_type< T >的type为T;
resolved_type< Future< T > >的type也为T。
无论是普通的T,还是Future< T >,通过resolved_type决议出的type成员都为T。

我们看以下另一个then的调用:

f.then(promise::bind(&handleCRspAndReturnClient, current));

其中handleCRspAndReturnClient的签名如下:

int handleCRspAndReturnClient(JceCurrentPtr current, const promise::Future<std::string>& future)

此时将用int来具现化模板参数R。

为了可进行链式调用,我们应该保证then返回的是一个Future,因此这时不能直接用R,而需要用Future< R >。如果R本身就是Future< T >,我们则可以通过resolved_type将T萃取出来。

5.5.2 then的函数体

首先明确下then的使命:
1.注册一个回调函数,来处理就绪的future;
2.将回调函数的返回值带回来,返回一个future给用户做链式调用。

函数体内的操作:
1.首先保证m_future已经初始化。
2.定义了一个Promise变量:

Promise<value_type> promise;

3.调用this->m_future->registerCallback()来注册我们传进来的callback函数,这里使用了bind、SequentialCallback进行了包装:

this->m_future->registerCallback(
    bind(&detail::SequentialCallback<R, T>::template run<R>,
             owned(new detail::SequentialCallback<R, T>(callback, promise))));

4.返回promise的future:

return promise.getFuture();

具体的细节这里暂不探讨,目前只需要明白,这里的promise承诺返回一个Future< value_type >而value_type是跟函数callback(比如handleBRspAndSendCReq)的返回值息息相关的(比如handleBRspAndSendCReq返回一个Future< std::string >,其value_type为string,handleCRspAndReturnClient返回一个int,其value_type为int)。

Promise和SequentialCallback的作用就是把callback的返回值给带回来,最终返回给用户来做链式调用。

2016/9/15更新:

带回callback的返回值

现在简单说下在then中,如何将callback的返回值带回来并生成一个future返回:

// then()中的代码片段
Promise<value_type> promise;

this->m_future->registerCallback(bind(
&detail::SequentialCallback<R, T>::template run<R>,
owned(new detail::SequentialCallback<R, T>(callback, promise))));

return promise.getFuture();

这里多引入了一层promise,该promise**承诺带回函数callback()的返回值**(如果该返回值是一个future,则还是带回一个future,如果不是future,比如int,则带回一个future< int >)。

可以看到,bind绑定的是SequentialCallback的成员函数run(template表示这是一个模板)。
通过第二个参数的SequentialCallback对象进行调用,该对象封装了callback和一个promise。

当上一层的future就绪时,会调用回调函数,此时调用的是SequentialCallback 的run函数,而不是真正的callback。在run函数中,再调用真正的callback进行处理,并将callback的返回值设置到SequentialCallback对象的promise当中,而then返回的正是这个promise关联的future。

因此,通过一层间接的future/promise,then成功地返回了callback的返回值的一个future。

Run有多个重载版本:

// For callback which returns void.
template <typename U>
typename enable_if<is_void<U> >::type run(const FuturePtr& future)
{
    try 
    {
        m_callback(future);
        m_promise.set();
    } 
    catch (...) 
    {
        m_promise.setException(currentException());
    }
}

当callback返回值为void时,无需返回值,所以promise调用空的set。

// For callback which returns non-void non-future type
template <typename U>
typename enable_if_c<!is_void<U>::value && !is_future_type<U>::value>::type
run(const FuturePtr& future)
{
    try 
    {
        m_promise.setValue(m_callback(future));
    } 
    catch (...) 
    {
        m_promise.setException(currentException());
    }
}

当callback返回值为非void且非future时,调用m_callback并将返回值设置到promise中,于是该值可以通过promise.getFuture().get()来获取。

// For callback which returns future type.
template <typename U>
typename enable_if<is_future_type<U> >::type run(const FuturePtr& future)
{
    try 
    {
        m_callback(future).then(
            bind(&ForwardValue<value_type>::template run<value_type>,
                owned(new ForwardValue<value_type>(m_promise))));
    } 
    catch (...) 
    {
        m_promise.setException(currentException());
    }
}

当callback返回值为future时,则对该future使用then绑定到ForwardValue的run函数当中(类似的手法),run函数中再通过get()方法把future内的值取出来,并设置到then中定义的promise当中,于是then的返回值便以一个future的形式存放着callback的返回值。

至于如何对类型进行判断,当然是利器traits技法,这里不再展开。

5.5.3 then函数总结

我们重新看一下下面这个例子,理清这两句代码包含的内容:

promise::Future<std::string> f = sendBReq(_pPrxB, sIn, current);
f.then(promise::bind(&handleBRspAndSendCReq,_pPrxC,current))
.then(promise::bind(&handleCRspAndReturnClient, current));

首先,我们在sendBReq中定义了一个Promise,并把它绑定到BServantCallback中。

当异步调用回包时,将回调BServantCallback,在里面调用Promise的setValue进行赋值。

赋值完毕之后,将调用通过then绑定的回调handleBRspAndSendCReq来处理。

由于我们通过promise.getFuture()使得f和promise的m_future**指向了同一个对象**,所以我们在回调handleBRspAndSendCReq中可以通过f.get()来读取该值。

f.get()只是完成了handleBRsp部分,在SendCReq的时候,类似于sendBReq,我又定义了一个Promise,我们需要把与之关联的future(通过promise.getFuture()获取)作为handleBRspAndSendCReq的返回值,并通过then中的Promise和SequentialCallback将这个future返回给用户,从而用户可以继续调用then来指定handle。

总结:then帮我们把handle回调函数注册到future当中,当future可读时,将调用该handle进行处理,then还为我们把handle的返回值带回来,以供链式调用。

5.6 future和promise的关系梳理

两者都有一个m_future成员,其类型为

SharedPtr<detail::FutureObjectInterface<T> >

这里写图片描述

由于为Future< T >需要针对void进行特化,为避免过多重复的代码,把与特化无关的部分抽离出来形成FutureBase作为基类。

从上图可以看出,Future和Promise均持有m_future,两者正是通过该对象进行共享、关联的(通过promise.getFuture()实现)。其中Promise对外提供了对m_future的set(写)接口,而Future对外提供了m_future的get(读)接口。

5.7 FutureObjectInterface的实现

下图展示了FutureObjectInterface的具体实现:

这里写图片描述

可以看到,FutureObjectInterface有FutureObject和PromptFutureObject两种实现。

Promise的FutureObjectInterface 是一个FutureObject。

Future的FutureObjectInterface有两种情况:直接用一个值来构造Future时(比如调用makeFuture来获取一个future)用的是PromptFutureObject,而其他情况(比如通过Promise获得的future)用的是FutureObject。

那么,两者有何区别呢?

对于第一个应用场景,future不是通过promise来获取的,而是直接用一个立即数构造:

explicit Future(typename detail::FutureTraits<T>::rvalue_source_type t)
: detail::FutureBase<T>(SharedPtr<detail::PromptFutureObject<T> >
(new detail::PromptFutureObject<T>(t)))
    {}

比如下面这个应用场景:

Future< int > hsF = makeFuture<int>(-1);    //使用立即数构造
if (openSwitch)
{
    // sendBReq中进行异步调用,并通过promise.getFuture()返回一个future
    hsF = sendBReq();
}
// …

在handle中,我们可以通过以下判断来决定是否处理:

int result = hsF.get();
if (result != -1)
{
    // handle
}

立即数构造这种用法,由于其值已经设定了,不需要等待promise填值进去,因此该future内部的PromptFutureObject是只读的,也就不需要加锁。如果还是使用FutureObject这个版本,将会在加锁解锁上做无用功。因此PromptFutureObject是针对这种场景进行优化的。

而当Future 与Promise共享同一个m_future时,由于Future和Promise可能在不同线程中,因此可能同时读写,这里存在race condition,因此需要加锁。FutureObject正是一个加锁的版本。

关于FutureObject,有几个需要注意的点:
1.在一开始介绍的时候我们说过,Future可以获取共享状态的值(通过get()方法),在必要的情况下阻塞调用者并等待共享状态标识变为ready,然后才能获取共享状态的值。
这里写图片描述
这里写图片描述

2.setValue只能被调用一次,即共享状态的值只能设置一次,如果试图设置第二次,将抛出异常。
这里写图片描述

3.在registerCallback时,根据m_is_done来判断是否已经setValue,如果m_is_done为true,则直接调用callback(this->sharedFromThis())来处理,否则将callback加入m_pending_callbacks列表中,等待setValue之后调用。在setValue中,除了设置值之外,还会调用doPendingCallbacks()函数,在该函数中逐个调用m_pending_callbacks列表中的callback。

最后,关于when_all的实现(并行异步调用),后续有时间再补充~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++异步调用利器future/promise实现原理 的相关文章

  • PPTV面试题——括号消除

    题目 xff1a 给定一个字符串 xff0c 设计一个算法消除其中承兑的括号 xff0c 如果括号不成对 xff0c 提示异常 xff08 error xff09 如 xff08 1 xff0c xff08 2 3 xff09 xff0c
  • reStructuredText(rst)简介+安装+简单说明

    reStructuredText简介 43 安装 43 简单说明 reStructuredText是什么 reStructuredText是扩展名为 rst的纯文本文件 xff0c 含义为 重新构建的文本 xff0c 是python编程语言
  • 【VS Code】"The 'clang-format' command is not available. Please check your clang-format."报错

    文章目录 34 The 39 clang format 39 command is not available 34 报错说明一 安装插件 clang format 二 解决方案三 参考链接 34 The clang format comm
  • 大环境不好,找工作太难?看我历时10天三面阿里,成功入职...

    三面大概九十分钟 xff0c 问的东西很全面 xff0c 需要做充足准备 xff0c 就是除了概念以外问的有点懵逼了 xff08 呜呜呜 xff09 回来之后把这些题目做了一个分类并整理出答案 xff08 强迫症的我狂补知识 xff09 分
  • Ubuntu18.04 远程命令行下安装并启动x11vnc

    最近做项目需要连接ubuntu的图像界面 xff0c 开始用XQuartZ xff0c 界面太low太丑 xff0c 而且有些应用启动不了 xff1b 后来转到使用x11vnc xff0c 在网络上查看了很多交安装x11vnc的方法 xff
  • CMakeLists.txt编写常用命令

    文章目录 一 设置cmake最小版本二 设置项目名称三 设置编译目标类型四 指定编译包含的源文件1 明确指明包含的源文件2 搜索指定目录的所有的cpp文件3 自定义搜索规则4 包含多个文件夹里的文件 五 设置包含目录六 设置链接库搜索目录七
  • Docker 拉取镜像及标签 pull | tag

    Docker 拉取镜像及标签 pull tag 重翻Fabric项目的源码 xff0c 发现Docker部分内容 xff0c 有很多不尽理解的地方 xff0c 看着看着 xff0c 就看到使用docker pull拉取Fabric镜像及使用
  • USB大容量存储设备无法启动该怎么办?

    USB大容量存储设备 xff08 USB mass storage device class xff0c 也称为USB MSC或UMS xff09 是一个协议 xff0c 允许一个USB接口的设备与电脑相连接 xff0c 以便在两者之间传输
  • Tensor数据相关的运算、函数讲解及与numpy区别

    Tensor tensorflow 中使用它来表示数据 可以看做多维数组或者list 标量是张量 xff0c 向量是张量 xff0c 矩阵是张量 xff0c 矩阵的矩阵是张量 常用几种定义方法 1 variable变量 xff0c 一般是可
  • QGC4.1.2二次开发(1)--Qt5.12.6 andorid开发环境搭建

    开发环境介绍 xff1a QGC版本 xff1a 4 1 2 Qt版本 xff1a 5 12 6 xff08 QGC要求 xff09 windows平台开发 xff1a vs2017 andorid平台 xff1a JDK Java SE
  • SITL--仿真多架无人机

    SITL仿真环境搭建 ardupliot源码下载与编译 首先需要安装Ardupliot开源飞控的开发环境 xff0c 参考这个知乎博主的文章 xff1a 链接 我的安装环境 ubuntu20 04 先下载Ardupilot源码 xff0c
  • QGC4.1.2二次开发(2)QGC连接与数据收发

    文章目录 前言一 连接原理二 连接过程与数据收发1 连接过程 xff08 以串口为例 xff09 2 数据发送 总结 前言 QGC连接无人机飞控时支持多种连接方式 xff0c 并且可以自动连接 xff0c 不由让人好奇它的实现原理 xff0
  • GRPC远程调用

    目录 FAQ gRPC1 gRPC原理 1 1 什么是RPC 1 2 gRPC的一些特性 1 3 gRPC支持的编程语言 1 4 gRPC的使用场景 1 5 谁在使用gRPC 1 6 gRPC设计之初的动机和原则 2 数据封装和数据传输问题
  • 算法导论->算法基础->2.1插入排序 (从小到大)

    1 伪代码 2 执行过程图 3 c语言实现完整代码 include lt stdio h gt include lt malloc h gt typedef struct MyArray int pbase int length MyArr
  • hihocoder图像算子(高斯消元)

    描述 在图像处理的技术中 xff0c 经常会用到算子与图像进行卷积运算 xff0c 从而达到平滑图像或是查找边界的效果 假设原图为H W的矩阵A xff0c 算子矩阵为D D的矩阵Op xff0c 则处理后的矩阵B大小为 H D 43 1
  • subs()函数()

    摘自matlab Utilities for obsolete MUTOOLS commands subs 既是目录也是函数 subs Symbolic substitution subs S OLD NEW replaces OLD wi
  • IEEE802

    IEEE 官方最新标准 xff1a Browse Standards Get Program IEEE Xplore https ieeexplore ieee org browse standards get program page s
  • 笔记本电脑3C认证要求的相关介绍

    作为CCC强制目录中的产品 xff0c 便携式计算机如果想在国内销售 xff0c 是必须要进行3C认证的 便携式计算机是什么 也就是笔记本电脑 xff0c 平板电脑等 官方的定义是以便携性为特点 xff0c 内置了输入输出设备 电池模块的微
  • 普通门禁卡及各类复制卡相关知识

    转自 xff1a https nfctool cn 42 本文带你了解M1卡的数据结构 xff0c 为以后的破解提供理论基础 同时带你了解各种IC卡 xff0c 让你对破解和复制有更清晰的目标 请注意 xff0c ID卡没有密码 xff0c
  • 用XDS510-V4专业版仿真器连接CCS3.3与28335问题记录

    今天用仿真器连接28335一直没连上 xff0c 错误有 xff1a 1 xff0c 断开仿真器用ccs3 3连接的时候显示为 xff08 不接仿真器 xff0c 空连接 xff09 Error connecting to the targ

随机推荐

  • NVIDIA Jetson TX2 通过vnc 桌面控制

    1 安装Xrdp Windows远程桌面使用的是RDP协议 xff0c 所以ubuntu上就要先安装Xrdp xff0c 在ubuntu软件中心搜索xrdp安装 安装xrdp的同时会自动安装vnc4server xbase clients组
  • NVIDIA Jetson TX2 查看系统参数状态

    1 xff0c 查看Jetson TX2 L4T版本 xff1a head n 1 etc nv tegra release 在刷 JetPack 3 0之前 和刷之后 版本参数发生细微的变化 xff1a REVISION xff1a 由
  • 解决 ImportError: No module named 'serial' 问题

    在pycharm里编写Python串口程序的时候 xff0c 编译时提示 ImportError No module named 39 serial 39 解决办法 xff1a 安装 serial module 这里区分python2和 p
  • 查看ubuntu下Qt的版本

    1 xff0c 查看ubuntu下Qt的版本 打开命令行输入 xff1a span style font size 14px qmake v span
  • 运算放大器基本运算

    转自 xff1a http www 21ic com jichuzhishi analog amplifier 2014 11 11 606654 html 运算放大器组成的电路五花八门 xff0c 令人眼花瞭乱 xff0c 是模拟电路中学
  • KEIL 注解和去注解 快捷键添加

    KEIL 注解和去注解 快捷键添加方法 xff1a 菜单栏Edit gt Configuration gt Shortcut Keys 1 例如设置 注解快捷键 xff1a Ctrl 43 2 例如设置 去注解快捷键 xff1a Ctrl
  • git、vscode免密登录

    1 git配置 git config global list 查看当前配置 git config global user name 34 xiaoyaozi 34 git config global user name 34 xiaoyao
  • 555 单稳态电路

    555 定时器成本低 xff0c 性能可靠 xff0c 只需要外接几个电阻 电容 xff0c 就可以实现多谐振荡器 单稳态 触发器及施密特触发器等脉冲产生与变换电路 它内部包括两个电压比较器 xff0c 三个5K欧姆的等值串联分压电阻 xf
  • Allegro 铺铜设置

    软件版本 xff1a Allegro16 6 敷铜 xff1a 放置禁止敷铜区域 xff1a Setup Areas Route Keepout 1 标题栏选Shap gt Global Dynamic Params Shape Polyg
  • OVP 过压保护电路

    过压保护电路 OVP 为下游电路提供保护 xff0c 使其免受过高电压的损坏 OVP电路监测外部电源 如 xff1a 离线电源或电池 的直流电压 xff0c 通过下述两种方式中的一种保护后续电路 xff1a 撬棍钳位电路或串联开关 撬棍电路
  • 超全蓝牙芯片原厂总结(含芯片型号)

    转自 xff1a https blog csdn net weixin 42583147 article details 80923946 作者 xff1a XCODER 蓝牙芯片原厂 1 CSR 高通 xff08 被高通收购 xff09
  • ST-Link的internal command error问题的解决方法

    问题 xff1a 显示 xff1a internal command error 这是由于stlink无法识别到芯片的情况 xff0c 通过解决这个问题我找到几个原因和解决方法 xff1a 1 xff0c 芯片睡眠 xff0c 停机 xff
  • 蓝牙 UUID 解释

    一 xff0c 什么是 UUID UUID 可以简单理解为编号 xff0c 唯一的编号 xff0c 用于区分不同的个体 服务和特性都有各自的UUID 比如经典的9527 UUID 就跟身份证一样 xff0c 不管是你是局长还是科长 xff0
  • 【人工智能】传教士和野人问题(M-C问题)

    摘要 本题需要解决的是一般情况下的传教士和野人问题 xff08 M C问题 xff09 通过对问题的一般化 xff0c 我们用一个三元组定义了问题的状态空间 xff0c 并根据约束条件制定了一系列的操作规则 xff0c 最后通过两个启发式函
  • 【算法设计与数据结构】为何程序员喜欢将INF设置为0x3f3f3f3f?

    在算法竞赛中 xff0c 我们常常需要用到一个 无穷大 的值 xff0c 对于我来说 xff0c 大多数时间我会根据具体问题取一个99999999之类的数 xff08 显得很不专业啊 xff01 xff09 在网上看别人代码的时候 xff0
  • 【slighttpd】基于lighttpd架构的Server项目实战(7)—http-parser

    对于http服务器 xff0c http request的解析是比较麻烦的 xff0c 由于我们的重点并不在这上面 xff0c 所以这一部分不打算自己编写 xff0c 而是使用开源的http parser库 xff0c 下面我们将使用该库来
  • select和epoll 原理概述&优缺点比较

    这个问题在面试跟网络编程相关的岗位的时候基本都会被问到 xff0c 刚刚看到一个很好的比喻 xff1a 就像收本子的班长 xff0c 以前得一个个学生地去问有没有本子 xff0c 如果没有 xff0c 它还得等待一段时间而后又继续问 xff
  • 笔记-关于神经网络黑盒模型可解释性,可视化

    原博地址 xff1a 深度学习黑盒可视化指南 xff0c 从隐藏层开始 摘 xff1a 一旦神经网络接收到相当大的所需数据集后 xff0c 该网络就会使用其精确的知识 权重 来证明或识别未知数据样本上的模式 即在经过大量数据集训练以后 xf
  • C++11 多线程 future/promise简介

    1 lt future gt 头文件简介 Classes std future std future error std packaged task std promise std shared futureFunctions std as
  • C++异步调用利器future/promise实现原理

    前言 在异步编程中 xff0c 各种回调将让人眼花缭乱 xff0c 代码分散 xff0c 维护起来十分困难 boost和C 43 43 11 的 future promise 提供了一个很好的解决方案 xff0c 使得代码更加漂亮 易维护