ROS2多线程的Python实现

本文介绍了ROS2中的并发概念,探讨了不同类型的执行器(如SingleThreadedExecutor和MultiThreadedExecutor),重点讲解了回调组(CallbackGroups)及其在多线程环境中的应用,包括MutuallyExclusiveCallbackGroups和ReentrantCallbackGroups。
摘要由CSDN通过智能技术生成

基础概念

  • 并发(Concurrency)意味着系统或软件可以同时运行许多任务,Concurrency
  • 线程(Thread)是可以由操作系统创建和管理的轻量级执行单元。每个线程都有自己的堆栈,并且可以与其他线程并发执行代码。

请添加图片描述

  • 例如,假设我们有一台单核 CPU 机器和两个并发执行的线程。操作系统将以快速的速度在两个线程之间切换,允许每个线程在切换到另一个线程之前执行一小段时间。此过程将重复,直到两个线程都完成其任务。我们将其称为并发机制。

执行器(Executor)的概念

  • ROS2 中的执行管理是通过执行器(Executor)的概念来解释的
  • Executor使用底层操作系统的一个或多个线程来调用传入消息和事件的订阅、计时器、服务服务器、操作服务器等的回调。
  • 我们来看一个普通的订阅节点
    import rclpy
    from rclpy.node import Node
    
    from std_msgs.msg import String
    
    
    class MinimalPublisher(Node):
    
        def __init__(self):
            super().__init__('minimal_publisher')
            self.publisher_ = self.create_publisher(String, 'topic', 10)
            self.timer = self.create_timer(0.5, self.timer_callback)
    
        def timer_callback(self):
            msg = String()
            msg.data = 'Hello World'
            self.publisher_.publish(msg)
    def main(args=None):
        rclpy.init(args=args)
    
        minimal_publisher = MinimalPublisher()
        rclpy.spin(minimal_publisher)
        minimal_publisher.destroy_node()
        rclpy.shutdown()
    
    
    if __name__ == '__main__':
        main()
    
  • 上述代码中
    rclpy.spin(minimal_publisher)
    
    • 实际上是对单线程执行器的实例化和调用
      executor=SingleThreadedExecutor()
      executor.add_node(minimal_publisher)
      executor.spin() # 和上述代码相同
      

执行者(Executor)的类型

  • 多线程执行器创建可配置数量的线程,以允许并行处理多个消息或事件。

  • 提供了三种 Executor 类型,派生自共享父类:
    请添加图片描述

  • SingleThreadedExecutor:就是默认使用的单线程执行器,同一时间下只能执行一个线程的任务,其他任务需要排队(阻塞式)

  • StaticSingleThreadedExecutor:在订阅、定时器、服务服务器、动作服务器等方面优化了扫描节点结构的运行时成本。它只在添加节点时执行一次扫描,而其他两个执行器定期执行扫描此类更改。因此,StaticSingleThreadedExecutor只能与在初始化期间创建所有订阅、计时器等的节点一起使用。

  • MultiThreadedExecutor:多线程执行器,本文要介绍的重点,但是直接使用它无法起到作用,这里还要引入回调组(Callback groups)的概念

回调组(Callback groups)的概念

  • ROS2 允许按组(groups)组织节点的回调。
  • 在rclpy中,通过调用特定回调组类型的构造函数来完成相同的操作。回调组必须在节点的整个执行过程中存储(例如作为类成员),否则执行器将无法触发回调。然后,可以在创建订阅、计时器等时指定此回调组 - 例如通过订阅选项:
    my_callback_group = MutuallyExclusiveCallbackGroup()
    my_subscription = self.create_subscription(Int32, "/topic", self.callback, qos_profile=1,callback_group=my_callback_group)
    
  • 在平常我们创建订阅、计时器等时没有制定回调组时,订阅、计时器会自动绑定默认的回调组(Node.default_callback_groups)

回调组(Callback groups)的类型

  • MutuallyExclusiveCallbackGroups: 该组的回调不能并行执行。也就同一时刻只有一个线程在运行。(默认的回调组),组中的回调由 SingleThreadedExecutor 执行一样。
    • 属于不同回调组(任何类型)的回调始终可以彼此并行执行。
      self.group1=MutuallyExclusiveCallbackGroup()
      self.group2=MutuallyExclusiveCallbackGroup()
      sub1= self.create_subscription(Int32, "/topic1", self.callback, qos_profile=1,callback_group=self.group1)
      sub2 = self.create_subscription(Int32, "/topic2", self.callback, qos_profile=1,callback_group=self.group2)
      executor=MultiThreadedExecutor(num_threads=3)
    
  • ReentrantCallbackGroups: 该组的回调可以并行执行。允许执行器以任何它认为合适的方式安排和执行组的回调,而没有任何限制。这意味着,除了不同的回调彼此并行运行之外,同一回调的不同实例也可以同时执行。
      self.group1=ReentrantCallbackGroups()
      sub1= self.create_subscription(Int32, "/topic1", self.callback, qos_profile=1,callback_group=self.group1)
      sub2 = self.create_subscription(Int32, "/topic2", self.callback, qos_profile=1,callback_group=self.group1)
      executor=MultiThreadedExecutor(num_threads=3)
    

历程

  • import rclpy
    from rclpy.node import Node
    from rclpy.callback_groups import ReentrantCallbackGroup
    from rclpy.executors import MultiThreadedExecutor
    class Test(Node):
       def __init__(self):
          super().__init__('test')
          self.group=ReentrantCallbackGroup()
          self.timer1=self.create_timer(0.1,self.timer1CB,callback_group=self.group)
          self.timer2=self.create_timer(1,self.timer2CB,callback_group=self.group)
          self.i=0
          self.executor=MultiThreadedExecutor(num_threads=3)
       def timer2CB(self):
          self.i+=1
       def timer1CB(self):
          print(self.i)
    def main(args=None):
      rclpy.init(args=args)
      test=Test()
      rclpy.spin(test)
      test.destory_node()
      rclpy.shutdown()
    if __name__=='__main__':
      main()
       ```
    

预告

*下期讲讲ROS2C++的多线程使用

参考文章

  • https://docs.ros.org/en/foxy/Tutorials/Beginner-Client-Libraries/Writing-A-Simple-Py-Publisher-And-Subscriber.html
  • https://docs.ros2.org/latest/api/rclpy/api/execution_and_callbacks.html
  • https://medium.com/@nullbyte.in/ros2-from-the-ground-up-part-5-concurrency-executors-and-callback-groups-c45900973fd2
  • https://docs.ros.org/en/foxy/Concepts/About-Executors.html
  • https://docs.ros.org/en/foxy/How-To-Guides/Using-callback-groups.html
  • 15
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
ROS(Robot Operating System)是一个用于构建机器人应用程序的开源框架。它提供了一系列工具、库和约定,用于简化机器人软件开发的过程。ROS中,多线程发布订阅是一常见的通信机制,用于实现不同节点之间的数据传。 在ROS中,发布者(Publisher)节点责将数据发布到特定的主题(Topic),而订阅者(Subscriber)节点则负责从主题中接收数据。多线程发布订阅允许多订阅者同时接收来自同一个主题的数据,从而提高系统的并发性能。 在Python使用ROS进行多线程发布订阅,可以按照以下步骤进行: 1. 导入所需的ROS库和模块: ```python import rospy from std_msgs.msg import String ``` 2. 初始化ROS节点: ```python rospy.init_node('node_name') ``` 3. 创建发布者对象,并指定要发布的主题和消息类型: ```python pub = rospy.Publisher('topic_name', String, queue_size=10) ``` 4. 创建订阅者回调函数,用于处理接收到的消息: ```python def callback(data): rospy.loginfo("Received: %s", data.data) ``` 5. 创建订阅者对象,并指定要订阅的主题和消息类型,以及回调函数: ```python sub = rospy.Subscriber('topic_name', String, callback) ``` 6. 编写发布数据的逻辑,并使用发布者对象发布消息: ```python rate = rospy.Rate(10) # 设置发布频率为10Hz while not rospy.is_shutdown(): msg = "Hello ROS!" pub.publish(msg) rate.sleep() ``` 以上是使用ROS进行多线程发布订阅的基本步骤。通过创建发布者和订阅者对象,并在回调函数中处理接收到的消息,可以实现节点之间的数据传输和通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值