본 블로그 포스팅은 https://www.youtube.com/@pinklab_studio/playlists
에서 현재 강의 중에 있는
ROS2 무작정 따라하기강의의 내용을 필자가 다시 복기하여 기록하는 내용에 관한 것이다.
이전 포스트 PinkLAB - 민형기 ROS2 응용 실습 7일차 - 커스텀 액션 서버
에서 언급했듯이 액션 서버기능이 포함된 1개의 노드를 SingleThreadExecutor
모드로 기동할 시 액션 서버의 콜백 함수는 execute_callback
모드로 동작하여 해당 콜백함수가 종료될 때까지 작업 스케쥴러를 독점하기에 다른 callback
함수의 이벤트 스케쥴을 처리할 수 없게 된다.
따라서 하나의 파이썬파일로 액션 서버를 포함한 다양한 기능을 구현하고 싶다면, 필연적으로 MultiThreadExecutor
모드로 코드를 작성해야 한다.
이 ROS2의 Multi Thread가 어떻게 기능하는지 이해하려면, Executor
에 대해 알아야 한다.
ROS2는 모든 작업의 실행 관리를 Executor
(실행자)를 통해 관리한다.
이 Executor
은 노드와 노드 내 여러 콜백함수의 실행 및 작업 스케쥴링을 관리하고, 해당 노드의 작업을 실행하는 연산기(Thread)를 어디에 할당할지를 결정하는 주체이다.
따라서 Executor
의 설정 방법이
1) SingleThreadedExecutor
: 하나의 Thread에 노드를 할당하여 노드의 각종 콜백 함수 스케쥴링을 관리하는 기본 모드
2) MultiThreadedExecutor
: 복수의 Thread를 병렬로 사용하며, 다수의 노드를 각각의 Thread에 할당하고 각 Thread는 매칭된 Node의 콜백 함수 스케쥴링을 관리하는 모드
3) StaticSingleThreadExecutor
: SingleThreadedExecutor
대비 더 고정된 상태로 콜백함수의 스케쥴링을 관리하는 모드 정말 실시간성이 중요하고 단순한 기능을 수행하는 노드를 설계할 때만 사용됨...
작업 개요는 위 사진과 같이 하나의 파이썬파일 : my_multi_thread.py에 2개의 노드를 MultiThreadedExecutor
모드를 통해 병렬 쓰레드를 구성하여 각각의 노드를 기동하며
각 노드는 아래와 같이 기능한다.
1) 1번 pub_node : PinkLAB - 민형기 ROS2 응용 실습 3일차 - 추가 노드 작성
포스트에서 작성한 my_first_package : my_publisher.py - TurtlesimPublisher 설계도(클래스)를 import
하여 해당 노드를 구현 터틀봇 제어
2) 2번 sub_node : PinkLAB - 민형기 ROS2 응용 실습 2일차 - 패키지 실행+노드작성
포스트에서 작성한 my_first_package : my_subscriber.py - TurtlesimSubscriber 설계도(클래스)를 import
하여 해당 노드를 구현 터틀봇 위치정보 수집
# 액션 서비스 서버 + 클라이언트 동시 구동 전
# 꼭 공부를 해 두어야 할 것이 멀티 스레드 임
import rclpy as rp
# 멀티스레딩 기능을 활성화 하기 위한 MultiThreadedExecutor
from rclpy.executors import MultiThreadedExecutor
from rclpy.node import Node
# 사전에 작성한 다른 패키지의 다른 노드 클래스를 import하는것 가능
# 상속받는 커스텀 패키지:커스텀노드:노드기능
# 1번 : my_first_package의 my_publisher에서 구현한 TurtlesimPublisher
from my_first_package.my_publisher import TurtlesimPublisher
# 2번 : my_first_package의 my_subscriber 구현한 TurtlesimSubscriber
from my_first_package.my_subscriber import TurtlesimSubscriber
def main(args=None):
rp.init()
#멀티 스레드에 할당할 노드 객체화
pub_node = TurtlesimPublisher()
sub_node = TurtlesimSubscriber()
#ROS2 스케줄링 관리 단위 : Executor을 멀티 프로세싱 모드로 객체화
executor = MultiThreadedExecutor()
#executor에 인스턴스화 한 노드항목 추가
executor.add_node(pub_node)
executor.add_node(sub_node)
try:
executor.spin()
# try절이 정상적으로 종료되었을 시 구동되는 구문
finally:
executor.shutdown()
pub_node.destroy_node()
sub_node.destroy_node()
rp.shutdown()
if __name__ == '__main__':
main()
알아야 할 개념 Executor
과 MultiThread
그리고 각 쓰레드에 설계한 노드의 할당은 전체 코드를 보면 알겠지만
매우 간단하다.
rclpy
패키지 executors
라이브러리 MultiThreadedExecutor
클래스를 import
하면 되며,
https://docs.ros2.org/galactic/api/rclpy/api/execution_and_callbacks.html
add_node
메서드로 인스턴스화 한 Node를 Executor
에 한개씩 할당하면 된다.
그리고 이전의 코드에서는 rp.spin([노드])
식으로 노드의 호출방식을 설정했다면
MultiThreadedExecutor
에서는 Executor.span()
으로 전체 Executor
의 호출 방식을 설정한다.
그리고 try
- finally
구문으로 코드 실행 종료에 따른 각 기능을 지정했다.
setup.py의 entry_point
에 신규로 작성한 my_multi_thread.py를 등록해준 뒤 패키지를 빌드하고 등록한다
$ colcon build --packages-select my_trd_pkg
# [my_trd_pkg]에 신규 노드가 추가됫으니 빌드
$ r2pkgsetup # [my_trd_pkg] 신규 노드 등록
코드 실행
1번 bash : 멀티 쓰레드 노드 실행
$ ros2 run my_trd_pkg my_multi_thread_node
2번 bash : rqt_graph
실행
$ humble
$ rqt_graph
하나의 파이썬 파일만 구동했을 뿐인데 MultiThreadedExecutor
으로 2개의 노드가 기능함을 알 수 있다.
이제 여기에 turtlesim_node
를 구동하여 동작상태를 확인하자
3번 bash : 터틀심 노드 구동
$ humble
$ ros2 run turtlesim turtlesim_node
정상적으로 pub_node
, sub_node
가 이전 포스트
PinkLAB - 민형기 ROS2 응용 실습 2일차 - 패키지 실행+노드작성
PinkLAB - 민형기 ROS2 응용 실습 3일차 - 추가 노드 작성
에서 작성한 퍼블리셔와 섭스크라이버 클래스 기능을 인스턴스화 하여
각각의 기능을 하나의 파이썬 파일 : my_multi_thread.py로 MultiThreadedExecutor
를 활용하여 구동하는 실습을 마무리했다.
물론 각각의 노드별로 따로 파이썬 파일을 만들어서 기본 모드인 SingleThreadedExecutor
로 코드 및 패키지 작성하는게 더 단순하긴 하나, 그렇게 기능을 쪼개는 식으로 패키지를 작성하는데는 분명 한계가 발생하기에
ROS2를 제대로 응용하려면 Multi Thread는 알아두어야 하긴 하다.
이게 나중에 임베디드에서 여러 종의 Sensor의 I/O를 핸들링 할 때 필수적으로 구현을 해야 하는 항목이긴 하다...