【问题标题】:Multithreading behaviour with ROS AsyncSpinnerROS AsyncSpinner 的多线程行为
【发布时间】:2018-01-29 09:08:03
【问题描述】:

我试图了解 ROS 的 AsyncSpinner 是如何工作的,因为我可能有一些误解。你可以找到类似的问题here

正如here 所见,其定义中提到:

异步微调器: 产生几个线程(可配置) 将并行执行回调而不阻塞线程 叫它。 start/stop 方法允许控制回调的时间 开始处理,何时停止。

在官方文档here 中,AsyncSpinning 也被标注为一种多线程 Spinning。

也就是说,我有一个非常简单的示例,发布者和订阅者使用 AsyncSpinner 来测试多线程行为。

#include "ros/ros.h"
#include "std_msgs/String.h"

int main(int argc, char **argv)
{
  ros::init(argc, argv, "publisher");
  ros::NodeHandle nh;

  ros::Publisher chatter_pub = nh.advertise<std_msgs::String>("chatter", 1000);

  ros::Rate loop_rate(10);
  while (ros::ok())
  {
    std_msgs::String msg;
    msg.data = "hello world";

    chatter_pub.publish(msg);

    ros::spinOnce();

    loop_rate.sleep();
  }

  return 0;
}

以及定义和使用微调器的订阅者:

#include "ros/ros.h"
#include "std_msgs/String.h"
#include <boost/thread.hpp>

int count = 0;

void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  count++;
  ROS_INFO("Subscriber %i callback: I heard %s", count, msg->data.c_str());
  sleep(1);
}

int main(int argc, char **argv)
{
  ros::init(argc, argv, "subscriber");
  ros::NodeHandle nh;

  ros::Subscriber sub = nh.subscribe("chatter", 1000, chatterCallback);

  ros::AsyncSpinner spinner(boost::thread::hardware_concurrency());
  ros::Rate r(10);

  spinner.start();
  ros::waitForShutdown();

  return 0;
}

当我运行这两个程序时,我得到以下输出:

[ INFO] [1517215527.481856914]: Subscriber 1 callback: I heard hello world
[ INFO] [1517215528.482005146]: Subscriber 2 callback: I heard hello world
[ INFO] [1517215529.482204798]: Subscriber 3 callback: I heard hello world

如您所见,回调每秒运行一次,并且没有并行调用其他回调。我知道全局回调队列正在执行,因为如果我停止发布者,订阅者将继续从队列中弹出累积的消息。

我知道我不应该阻止回调,但在上面的定义中指出,这不会停止调用它的线程,我猜其他人也不会停止由微调器创建的线程。我是否只是因为我阻止了回调而阻止了下一个回调?我有什么误解吗?我有点困惑,无法证明回调是并行运行的。也许你还有另一个例子?

【问题讨论】:

  • 你检查过boost::thread::hardware_concurrency()的返回值了吗?
  • boost::thread::hardware_concurrency() 返回 8 但即使我将 0 传递给构造函数,这意味着微调器为每个内核创建一个线程,我得到相同的结果。

标签: c++ multithreading callback ros


【解决方案1】:

简答:

ROS 回调默认是线程安全的。这意味着注册的回调只能由一个线程处理,并发调用被禁用。第二个线程不能同时访问同一个回调。

如果您注册第二个回调,您将看到微调器按预期工作,并且多个线程同时调用您的回调。

ros::Subscriber sub1 = nh.subscribe("chatter", 1000, chatterCallback);
ros::Subscriber sub2 = nh.subscribe("chatter", 1000, chatterCallback);

扩展答案:

异步微调器会以允许的速率尽可能快地尝试call 回调队列中的可用回调。如果回调已经在处理中(由另一个线程),CallResultTryAgain。这意味着稍后将开始新的尝试。

implementation of this lock 使用变量allow_concurrent_callbacks_,这意味着此行为是可选的。

解决方案:

可以通过设置正确的SubscribeOptions.allow_concurrent_callbacks 来允许并发调用,默认为false。因此您需要定义自己的SubscribeOptions。这是您需要订阅并允许并发回调调用的代码:

ros::SubscribeOptions ops;
ops.template init<std_msgs::String>("chatter", 1000, chatterCallback);
ops.transport_hints = ros::TransportHints();
ops.allow_concurrent_callbacks = true;
ros::Subscriber sub = nh.subscribe(ops);

【讨论】:

  • 你完全正确。这将帮助我安静很多。谢谢!
  • 请注意:线程安全的含义略有不同。只有当回调不访问共享数据或使用适当的同步机制来访问此类数据时,回调才是线程安全的。确实,默认情况下,没有两个线程可以同时运行相同的回调。
  • 如果队列看起来像“AABB”,异步微调器是否能够调用回调队列中的另一个可用回调,或者是否在回调队列上应用了锁?
  • 是的,锁是针对特定回调应用的。如果有足够的线程可用,则将调用队列的下一个解锁回调。
  • 应该可以添加新的回调。
猜你喜欢
  • 2018-04-24
  • 2015-11-05
  • 2012-02-29
  • 2017-09-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-07
  • 1970-01-01
相关资源
最近更新 更多