【问题标题】:Using member shared_ptr from a member callback function running in different thread (ROS topic subscription)使用在不同线程中运行的成员回调函数中的成员 shared_ptr(ROS 主题订阅)
【发布时间】:2020-08-06 22:22:52
【问题描述】:

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

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

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. 起初我在构造函数中进行了订阅调用。我认为在构造函数返回之前将 this 指针指向另一个线程可能很糟糕,所以我将它移到了 start 函数中,该函数在构造对象后调用。
  2. 看来shared_ptrs 的线程安全有很多方面。起初我在回调中直接使用了mutexlast_received_msg。现在我已将它们复制到局部变量中,希望这会有所帮助。但这似乎没有什么不同。
  3. 我添加了一个局部整数变量。我可以从回调中读取我在构造函数中分配给这个变量的整数。只是一个健全性检查,以确保回调实际上是在我的构造函数创建的实例上调用的。

【问题讨论】:

  • 我想重现您的问题。您能否制作一个minimal reproducible example,我可以将其复制并粘贴到foo.cpp 文件中,编译并自己查看问题?我很确定如果我尝试编写自己的代码来重现我不会放入不当行为的问题。

标签: c++ callback thread-safety smart-pointers ros


【解决方案1】:

我想我已经解决了问题。

订阅时,我将this 指针与回调一起传递给订阅函数。如果 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&lt;void(const T)&gt; 类型的变量。可能是由于subscribe 函数的方式。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-05-07
    • 1970-01-01
    • 2017-03-30
    • 1970-01-01
    • 1970-01-01
    • 2020-01-23
    • 1970-01-01
    相关资源
    最近更新 更多