提问者:小点点

使用运行在不同线程中的成员回调函数中的成员shared_ptr(ROS主题订阅)


我不完全确定如何最好地命名这个问题,因为我不完全确定问题的实质是什么(我猜“如何修复SegFault”不是一个好的标题)。

情况是,我写了这段代码:

template <typename T> class LatchedSubscriber {
private:
  ros::Subscriber sub;
  std::shared_ptr<T> last_received_msg;
  std::shared_ptr<std::mutex> mutex;
  int test;

  void callback(T msg) {
    std::shared_ptr<std::mutex> thread_local_mutex = mutex;
    std::shared_ptr<T> thread_local_msg = last_received_msg;

    if (!thread_local_mutex) {
      ROS_INFO("Mutex pointer is null in callback");
    }
    if (!thread_local_msg) {
      ROS_INFO("lrm: pointer is null in callback");
    }
    ROS_INFO("Test is %d", test);

    std::lock_guard<std::mutex> guard(*thread_local_mutex);

    *thread_local_msg = msg;
  }

public:
  LatchedSubscriber() {
    last_received_msg = std::make_shared<T>();
    mutex = std::make_shared<std::mutex>();
    test = 42;

    if (!mutex) {
      ROS_INFO("Mutex pointer is null in constructor");
    }
    else {
      ROS_INFO("Mutex pointer is not null in constructor");
    }

    
  }

  void start(ros::NodeHandle &nh, const std::string &topic) {
    sub = nh.subscribe(topic, 1000, &LatchedSubscriber<T>::callback, this);
  }

  T get_last_msg() {
    std::lock_guard<std::mutex> guard(*mutex);
    return *last_received_msg;
  }
};

本质上,它所做的是订阅一个主题(通道),这意味着每次消息到达时都会调用一个回调函数。 这个类的任务是存储最后接收到的消息,这样类的用户总是可以访问它。

在构造函数中,我为消息和互斥体分配了一个shared_ptr来同步对此消息的访问。 这里使用堆内存的原因是,可以复制LatchedSubscriber,并且仍然可以读取相同的锁存消息。 (subscriber已经实现了这种行为,其中复制它不会做任何事情,只是一旦最后一个实例超出作用域就停止调用回调)。

问题基本上是代码分段出错。 我很肯定这是因为我的共享指针在回调函数中变成null,尽管在构造函数中不是null。

ROS_INFO调用print:

Mutex pointer is not null in constructor
Mutex pointer is null in callback
lrm: pointer is null in callback
Test is 42 

我不明白怎么会这样。 我猜我不是误解了共享指针,ros主题订阅,就是两者兼而有之。

我做过的事:

  1. 一开始,我让subscribe调用发生在构造函数中。 我认为在构造函数返回之前将这个指针指定到另一个线程可能是不好的,因此我将其移到一个start函数中,该函数在构造对象之后调用。
  2. shared_ptrs的线程安全似乎有很多方面。 最初,我在回调中直接使用mutexlast_received_msg。 现在我已经将它们复制到局部变量中,希望这会有所帮助。 但这似乎没什么区别。
  3. 我添加了一个局部整数变量。 我可以从回调中读取我在构造函数中分配给这个变量的整数。 只是一个健全性检查,以确保回调实际上是在我的构造函数创建的实例上调用的。

共1个答案

匿名用户

我想我已经把问题想通了。

订阅时,我将this指针连同回调一起传递到subscribe函数。 如果复制了LatchedSubscriber并且删除了原始文件,则该this指针将无效,但sub仍然存在,因此将继续调用回调。

我认为这种情况不会发生在代码中的任何地方,但LatcedSubscriber作为成员存储在由唯一指针拥有的对象中。 看起来make_unique可能正在内部进行某些复制? 在任何情况下,为回调使用this指针都是错误的。

我最终做了以下事情

void start(ros::NodeHandle &nh, const std::string &topic) {
    auto l_mutex = mutex;
    auto l_last_received_msg = last_received_msg;

    boost::function<void(const T)> callback =
        [l_mutex, l_last_received_msg](const T msg) {
          std::lock_guard<std::mutex> guard(*l_mutex);
          *l_last_received_msg = msg;
        };
    sub = nh.subscribe<T>(topic, 1000, callback);
 }

这样,两个智能指针的副本将与回调一起使用。

将闭包分配给boost::function类型的变量似乎是必要的。 可能是因为subscribe函数的方式。

这似乎解决了这个问题。 我还可能再次将订阅移到构造函数中,并删除start方法。