Использование члена shared_ptr из функции обратного вызова члена, работающей в другом потоке (подписка ROS topi c) - PullRequest
0 голосов
/ 06 августа 2020

Я не совсем уверен, как лучше всего назвать этот вопрос, поскольку я не совсем уверен, какова на самом деле природа проблемы (я думаю, «как исправить 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;
  }
};

По сути, он подписывается на topi c (канал), что означает, что функция обратного вызова вызывается каждый раз, когда приходит сообщение. Задача этого класса - сохранить последнее полученное сообщение, чтобы пользователь класса всегда мог получить к нему доступ.

В конструкторе я выделяю shared_ptr для сообщения и для мьютекса для синхронизации доступа к этому сообщению. Причина использования кучи памяти здесь в том, что LatchedSubscriber может быть скопировано, и то же зафиксированное сообщение все еще может быть прочитано. (Subscriber уже реализует такое поведение, при котором его копирование ничего не делает, кроме того факта, что обратный вызов перестает вызываться, как только последний экземпляр выходит за пределы области действия).

Проблема в основном в том, что ошибка кода. Я почти уверен, что причина этого в том, что мои общие указатели становятся 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 topi c или обоих.

Что я сделал:

  1. Сначала у меня был вызов подписки, происходивший в конструктор. Я думаю, что указывать указатель this на другой поток до того, как конструктор вернется, может быть плохим, поэтому я переместил его в функцию start, которая вызывается после создания объекта.
  2. Есть много аспектов к потокобезопасности shared_ptr s кажется. Сначала я использовал mutex и last_received_msg непосредственно в обратном вызове. Теперь я скопировал их в локальные переменные, надеясь, что это поможет. Но, похоже, это не имеет значения.
  3. Я добавил локальную целочисленную переменную. Я могу прочитать целое число, присвоенное этой переменной в конструкторе, из обратного вызова. Просто проверка работоспособности, чтобы убедиться, что обратный вызов действительно вызывается для экземпляра, созданного моим конструктором.

1 Ответ

0 голосов
/ 06 августа 2020

Думаю, я понял проблему.

При подписке я передаю указатель 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<void(const T)>. Вероятно, из-за того, как работает функция subscribe.

Похоже, проблема решена. Я мог бы снова переместить подписку в конструктор и избавиться от метода start.

...