Ros

NodeHandle、Subscriber和Subscriber::Impl

调用NodeHandle::subscribe()可得到Subscriber对象,同时会在Ros系统创建一个订阅连接,这里将描述NodeHandle、Subscriber、Subscriber::Impl如何管理这个连接。对Subscriber的相关结论可以类推到Publisher、ServiceServer、ServiceClient。

 

  1. 可认为Subscriber由两部分组成,一是订阅连接,二是壳。变量Subscriber::Impl_表示订阅连接,类型shared_ptr<Subscriber::Impl>,其它的合称为壳。
  2. Subscriber::Impl_不是nullptr时,并不表示连接就有效,它可能已经被销毁(unsubscribe)。判断是否有效须使用Impl::isValid()。如果只拥有一个Subscriber对象指针,目前没办法通过该指针判断该Subscriber是否持有订阅连接。
  3. Ros以shared_ptr<Subscriber::Impl>管理订阅连接。NodeHandle::subscribe()时引用计数赋初值1,该Subscriber赋值给另一个Subscriber时,引用计数加1。~Subscriber让引用计数减1,减到0时,会销毁该连接。
  4. 销毁订阅连接指的是该连接从Ros中消失,消失不是要求析构掉之前持有过该连接的所有Subscriber,如何不析构Subscriber而销毁连接,使用Subscriber::shutdown。
  5. 要保证订阅连接(Subscriber::Impl)一直有效,至少得一直存在一个持有该连接的Subscriber。甚至可以没有创建该Subscriber的NodeHandle。
  6. Subscriber::shutdown()的作用是强制销毁(unsubscribe)内中连接,它不管Subscriber::Impl_当前有多少引用计数。一旦销毁,对之前持有过该连接的Subscriber,它们中Impl不会变成nullptr,是Impl一些成员被置为nullptr。如果调用shutdown时,Subscriber已没有有效连接,那它啥也不干。
  7. NodeHandle拥有Subscriber::Impl_的weak_ptr,放在数组变量NodeHandle::collection_->subs_。持有这个指针目的:在执行NodeHandle::shutdown()时,可以强制销毁掉subs_中依旧有效的连接(通过直接调用Impl::unsubscribe)。但是,若没特殊要要,不会调用NodeHandle::shutdown,于是可认为NodeHandle和存储订阅连接无关。注:NodeHandle::shutdown()不是减少Subscriber::Impl_的引用计数,而是销毁。
  8. 销毁订阅连接后,NodeHandle::collection_->subs_依旧保存着对它的weak_ptr。
  9. NodeHandle::~NodeHandle不会调用NodeHandle::shutdown(),或许,就不该存在NodeHandle::shutdown。
  10. TopicManager::subscriptions_存储着该进程subscribe过的订阅连接(关于TopicManager::subscriptions_参考“Publisher::publish源码分析”)。通过(Impl_.topic_+Impl_.helper_),Subscriber::Impl_从subscriptions_定位出自已。执行Impl::unsubscribe时,会把helper_置nullptr,unsubscribed_置true,但不会修改topic_。也就是说,不要根据Subscriber::Impl_.topic_是不是空来判断该订阅连接是否有效,须使用Impl::isValid()。
  11. 有两种方法可销毁订阅连接。一是用一个持有该连接的Subscriber,调用成员方法shutdown。二是析构掉持有该连接的所有Subscriber,使得Subscriber::Impl引用计数是0,导致调用~Impl,~Impl会执行销毁(unsubscribe)。
  12. 订阅连接一旦被销毁,TopicManager::subscriptions_中就没它的信息了,CallbackQueue收到msg时,自然不会再回调该连接中的回调操作。
  13. 不必担心先析构NodeHandle、后析构Subscriber会导致问题。由于Subscriber::Impl内持有一个“新”NodeHandle,“新”字加引号,因为它是由传入的NodeHandle复制创建。析构NodeHandle后,不会影响Impl内的NodeHandle,有这个NodeHandle,后面析构Subscriber时不会出问题。
  14. 系统中最后一个NodeHandle被析构时,~NodeHandle会调用ros::shutdown()。ros::shutdown和订阅连接没任何关系,在调用它前,系统应该已没订阅连接。不要主动调用ros::shutdown(),应该由Ros判断出这是最后一次析构NodeHandle时自动调用。假设系统只存在两个变量,一个NodeHandle,一个Subscriber,会出现析构NodeHandle时不调用ros::shutdown,析构Subscriber时反而调用。因为析构Subscriber导致析构Impl,析构Impl导致析构内中的NodeHandle,这内中的NodeHandle才是最后一个。

 

一、NodeHandle::subscribe(SubscribeOptions& ops)

<libros>/ros_comm/roscpp/src/libros/node_handle.cpp
------
Subscriber NodeHandle::subscribe(SubscribeOptions& ops)
{
  ......

  if (TopicManager::instance()->subscribe(ops))
  {
    Subscriber sub(ops.topic, *this, ops.helper);

    {
      boost::mutex::scoped_lock lock(collection_->mutex_);
      collection_->subs_.push_back(sub.impl_);
    }

    return sub;
  }

  return Subscriber();
}

bool TopicManager::subscribe(const SubscribeOptions& ops)

NodeHandle::subscribe填充好ops后,调用TopicManager::instance()->subscribe,输入参数ops冠上const修饰符,即TopicManager::subscribe不会修改ops,NodeHandle::subscribe是根据判断返回值知道subscribe是否成功订阅。一旦成功,会根据此次连接的关键信息“topic+helper”生成Impl。注意生成Impl的第二个参数NodeHandle,因为Impl在存储它时会创建一个新的,而不是引用计数加一。所以在析构Impl时,是销毁这个新NodeHandle,而不是构造时传入的nh。

sub.impl_存储着此次订阅连接,impl_类型是shared_ptr<Impl>,但经过push_back后,存在subs_中的将变成weak_ptr<Impl>。

1.1 NodeHandle.collection_.subs_

<libros>/include/ros/node_handle.h
------
class ROSCPP_DECL NodeHandle {
  NodeHandleBackingCollection* collection_;
};

<libros>/ros_comm/roscpp/src/libros/node_handle.cpp
------
class NodeHandleBackingCollection
{
public:
  typedef std::vector<Publisher::ImplWPtr> V_PubImpl;
  typedef std::vector<ServiceServer::ImplWPtr> V_SrvImpl;
  typedef std::vector<Subscriber::ImplWPtr> V_SubImpl;
  typedef std::vector<ServiceClient::ImplWPtr> V_SrvCImpl;
  V_PubImpl pubs_;
  V_SrvImpl srvs_;
  V_SubImpl subs_;
  V_SrvCImpl srv_cs_;

  boost::mutex mutex_;

  // keep shared_ptrs to these managers to avoid assertions. Fixes #838
  TopicManagerPtr keep_alive_topic_manager = TopicManager::instance();
  ServiceManagerPtr keep_alive_service_manager = ServiceManager::instance();
};

在NodeHandle内部,Subscriber::Impl_放在成员变量collection_->subs_,类型变成了Subscriber::ImplWPtr,让看ImplWPtr定义。

<libros>/include/ros/subscriber.h
------
class ROSCPP_DECL Subscriber
{
  class Impl
  {
  public:
    Impl();
    ~Impl();

    void unsubscribe();
    bool isValid() const;

    std::string topic_;
    NodeHandlePtr node_handle_;
    SubscriptionCallbackHelperPtr helper_;
    bool unsubscribed_;
  };
  typedef boost::shared_ptr<Impl> ImplPtr;
  typedef boost::weak_ptr<Impl> ImplWPtr;

  ImplPtr impl_;
};

Subscriber::ImplWPtr是指向Subscriber::Impl的boost::weak_ptr。也就是说,它不会增加、减少Subscriber::ImplPtr引用计数,只是像旁观者那样观察Subscriber::ImplPtr使用情况,必要时可通过lock方法得到ImplPtr。

由于NodeHandle拥有Subscriber::ImplWPtr,在需要时,它会通过lock判断该Subscriber::ImplWPtr是否还有效,有可能会执行操作,像销毁(unsubscribe),见底下的NodeHandle::shutdown()。

subs_变量类型是std::vector<Subscriber::ImplWPtr>,不能依此就得出结论,说此个NodeHandle拥有多少个Subscriber::Impl,此个std::vector就是多少长度,结论应该“Impl数 <= vector.size”。一旦出现有Subscriber::Impl已经被unsubscriber,系统中就不存在该连接了,但不会从vector中erase。须通过lock方法判断vector中的Impl是否还有效,正如shutdown。

<libros>/ros_comm/roscpp/src/libros/node_handle.cpp
------
void NodeHandle::shutdown()
{
  {
    NodeHandleBackingCollection::V_SubImpl::iterator it = collection_->subs_.begin();
    NodeHandleBackingCollection::V_SubImpl::iterator end = collection_->subs_.end();
    for (; it != end; ++it)
    {
      Subscriber::ImplPtr impl = it->lock();

      if (impl)
      {
        impl->unsubscribe();
      }
    }
  }
  ......
}

综上所述,NodeHandle拥有Subscriber::Impl_的weak_ptr,放在数组变量NodeHandle::collection_->subs_。持有这个指针目的:在执行NodeHandle::shutdown()时,可以强制销毁掉subs_中依旧有效的连接(通过直接调用Impl::unsubscribe)。但是,若没特殊要要,不会调用NodeHandle::shutdown,于是可认为NodeHandle和存储订阅连接无关。

 

二、销毁订阅连接:Subscriber析构和Subscriber::shutdown

Impl::unsubscribe执行销毁订阅连接,Subscriber析构和Subscriber::shutdown都会调用到它,都可达到销毁连接这个目的,但途径不一样。

<libros>/ros_comm/roscpp/src/libros/subscriber.cpp
------
void Subscriber::Impl::unsubscribe()
{
  if (!unsubscribed_) {
    unsubscribed_ = true;
    TopicManager::instance()->unsubscribe(topic_, helper_);
    node_handle_.reset();
    helper_.reset();
  }
}

Subscriber::Impl::~Impl()
{
  unsubscribe();
}

void Subscriber::shutdown()
{
  if (impl_) {
    impl_->unsubscribe();
  }
}
  • 销毁之Subscriber析构。Impl_是Subscriber成员变量,析构Subscriber时,会使Impl_引用计数减1,当减少到0时会调用~Impl。因为~Impl会调用unsubscribe,于是导致了销毁。
  • 销毁之Subscriber::shutdown。它不管impl_当前引用计数是多少,强制销毁。

Impl::unsubscribe()销毁订阅连接时,要调用TopicManager::instance()->unsubscribe,后者通过此次连接的关键信息“topic+helper”,在TopicManager::subscriptions_定位出自已所在的Subscription,并从Subscription::callbacks_删除自个信息,如果删除后callbacks_空了,意味着进程内已没订阅该topic的Subscriber,同时删除Subscription。

全部评论: 0

    写评论: