1261.46 단일 스레드 실행자와 멀티 스레드 실행자

1. 실행자 유형 선택의 공학적 의의

ROS2에서 실행자(Executor)는 노드에 등록된 콜백 함수들의 스케줄링과 실행을 담당하는 런타임 구성요소이다. 실행자의 유형에 따라 콜백 함수의 실행 모델이 순차적(sequential) 또는 병렬적(parallel)으로 결정되며, 이는 로봇 행동 제어 시스템의 응답 시간(response time), 처리량(throughput), 결정론성(determinism)에 직접적인 영향을 미친다. ROS2 rclcpp 라이브러리는 SingleThreadedExecutor, MultiThreadedExecutor, StaticSingleThreadedExecutor, 그리고 실험적으로 도입된 EventsExecutor를 제공한다.

2. 단일 스레드 실행자(SingleThreadedExecutor)

2.1 동작 원리

SingleThreadedExecutor는 하나의 스레드에서 모든 콜백을 순차적으로 실행하는 실행자이다. 내부적으로 대기 집합(wait set)을 구성하여 준비 완료(ready) 상태의 엔티티를 식별한 후, 해당 엔티티에 연결된 콜백을 하나씩 순차적으로 호출한다. 한 콜백의 실행이 완전히 종료된 후에야 다음 콜백의 실행이 시작된다.

#include "rclcpp/rclcpp.hpp"

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<rclcpp::Node>("single_thread_node");

  rclcpp::executors::SingleThreadedExecutor executor;
  executor.add_node(node);
  executor.spin();

  rclcpp::shutdown();
  return 0;
}

위 코드에서 executor.spin()은 단일 스레드에서 무한 루프를 실행하며, 등록된 노드의 모든 콜백을 순차적으로 처리한다.

2.2 스케줄링 특성

단일 스레드 실행자의 스케줄링 주기(scheduling cycle)는 다음의 단계로 구성된다.

  1. rcl_wait() 호출을 통해 대기 집합에 등록된 모든 엔티티의 준비 상태를 확인한다.
  2. 준비 완료된 엔티티의 목록을 수집한다.
  3. 수집된 목록에서 하나의 콜백을 선택하여 실행한다.
  4. 해당 콜백의 실행이 완료되면 다음 준비 완료 콜백을 선택하여 실행한다.
  5. 모든 준비 완료 콜백의 처리가 완료되면 1단계로 복귀한다.

이 과정에서 콜백 선택 순서는 대기 집합 내에서의 엔티티 등록 순서에 기반하며, 타이머, 구독자, 서비스, 클라이언트, 가드 조건의 순서로 우선 처리된다.

2.3 장점

단일 스레드 실행자의 주된 장점은 다음과 같다.

장점설명
경쟁 조건 배제모든 콜백이 동일 스레드에서 순차 실행되므로, 공유 자원에 대한 경쟁 조건(race condition)이 원천적으로 발생하지 않는다.
동기화 불필요뮤텍스(mutex), 조건 변수(condition variable) 등 스레드 동기화 기법의 적용이 불필요하다.
디버깅 용이성콜백 실행 순서가 결정론적이므로 재현 가능한 디버깅이 가능하다.
구현 단순성동시성 관련 오류를 고려할 필요가 없어 구현 복잡도가 낮다.

2.4 한계

단일 스레드 실행자의 구조적 한계는 다음과 같다.

  1. 처리량 제한: 하나의 콜백이 장시간 실행되면 다른 모든 콜백의 실행이 지연된다. 예를 들어, 영상 처리 콜백이 50ms를 소요하면 제어 루프 콜백의 주기가 최소 50ms 이상 지연될 수 있다.
  2. 차단 전파(blocking propagation): 네트워크 입출력, 파일 입출력 등 차단 호출(blocking call)이 포함된 콜백이 존재하면, 전체 시스템의 반응성이 저하된다.
  3. 다중 코어 미활용: 현대의 멀티코어 프로세서 아키텍처에서 단일 스레드만 사용하므로 하드웨어 자원의 활용률이 낮다.
  4. 실시간성 한계: 최악 응답 시간(worst-case response time)이 모든 콜백의 최악 실행 시간 합산에 비례하여 증가한다.

3. 멀티 스레드 실행자(MultiThreadedExecutor)

3.1 동작 원리

MultiThreadedExecutor는 스레드 풀(thread pool)을 사용하여 준비 완료된 콜백들을 복수의 스레드에서 병렬로 실행하는 실행자이다. 생성 시 스레드 수를 지정할 수 있으며, 기본값은 std::thread::hardware_concurrency()에 의해 결정되는 하드웨어 동시 실행 스레드 수이다.

#include "rclcpp/rclcpp.hpp"

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<rclcpp::Node>("multi_thread_node");

  // 4개의 스레드로 실행자 구성
  rclcpp::executors::MultiThreadedExecutor executor(
    rclcpp::ExecutorOptions(), 4);
  executor.add_node(node);
  executor.spin();

  rclcpp::shutdown();
  return 0;
}

3.2 스케줄링 특성

멀티 스레드 실행자의 각 스레드는 독립적으로 다음의 과정을 수행한다.

  1. 대기 집합에서 준비 완료된 엔티티를 하나 획득한다. 이 과정에서 내부 뮤텍스를 통해 다른 스레드와의 충돌을 방지한다.
  2. 해당 엔티티의 콜백 그룹(callback group) 실행 정책을 확인한다.
  3. 상호 배타적 콜백 그룹(MutuallyExclusiveCallbackGroup)에 속하는 경우, 해당 그룹의 잠금을 획득한 후 콜백을 실행한다.
  4. 재진입 콜백 그룹(ReentrantCallbackGroup)에 속하는 경우, 잠금 없이 즉시 콜백을 실행한다.
  5. 콜백 실행이 완료되면 1단계로 복귀한다.

3.3 콜백 그룹과의 상호 작용

멀티 스레드 실행자의 동작은 콜백 그룹의 유형에 의해 본질적으로 결정된다. 다음의 조합에 따라 실행 양상이 달라진다.

콜백 그룹 유형동일 그룹 내 콜백서로 다른 그룹 간 콜백
MutuallyExclusive순차 실행 (동시 실행 불가)병렬 실행 가능
Reentrant병렬 실행 가능병렬 실행 가능

기본 콜백 그룹은 상호 배타적 유형이므로, 콜백 그룹을 명시적으로 설정하지 않은 노드에서 멀티 스레드 실행자를 사용하더라도 해당 노드의 모든 콜백은 순차적으로 실행된다. 병렬 실행의 이점을 활용하기 위해서는 의도적으로 별도의 콜백 그룹을 생성하고 할당하여야 한다.

3.4 장점

장점설명
병렬 처리독립적인 콜백들이 동시에 실행되어 전체 처리량이 향상된다.
차단 내성하나의 콜백이 차단되더라도 다른 스레드에서 나머지 콜백의 실행이 계속된다.
다중 코어 활용멀티코어 프로세서의 계산 자원을 효율적으로 활용한다.
응답 시간 개선긴급 콜백이 장시간 실행 콜백으로 인해 지연되는 상황을 완화한다.

3.5 한계와 주의사항

  1. 동기화 복잡도: 재진입 콜백 그룹을 사용할 경우, 공유 자원에 대한 명시적인 동기화(뮤텍스, 원자적 연산 등)가 필수적이다.
  2. 교착 상태 위험: 상호 배타적 콜백 그룹 내에서 동기적 서비스 호출을 수행하면 교착 상태(deadlock)가 발생할 수 있다. 서비스 응답 콜백이 같은 그룹의 잠금에 의해 차단되기 때문이다.
  3. 스케줄링 비결정론성: 운영체제의 스레드 스케줄러에 의해 실제 실행 순서가 결정되므로, 콜백 실행 순서의 결정론성이 보장되지 않는다.
  4. 문맥 전환 오버헤드: 스레드 수가 가용 프로세서 코어 수를 초과하면 문맥 전환(context switching) 오버헤드가 증가한다.

4. 정적 단일 스레드 실행자(StaticSingleThreadedExecutor)

StaticSingleThreadedExecutorSingleThreadedExecutor의 최적화 변형이다. 표준 단일 스레드 실행자는 매 스케줄링 주기마다 대기 집합을 재구성(reconstruct)하는 반면, 정적 단일 스레드 실행자는 노드 추가 시점에 대기 집합을 단 한 번 구성하고 이후에는 재구성을 수행하지 않는다.

이 차이는 등록된 엔티티의 수가 많은 경우에 유의미한 성능 향상을 제공한다. Casini 등(2019)의 분석에 따르면, 표준 실행자의 대기 집합 재구성은 O(n)의 시간 복잡도를 가지며, 이는 콜백 수 n이 증가함에 따라 스케줄링 오버헤드가 선형적으로 증가함을 의미한다. 정적 실행자는 이 오버헤드를 O(1)로 상각(amortize)한다.

rclcpp::executors::StaticSingleThreadedExecutor executor;
executor.add_node(node_a);
executor.add_node(node_b);
// 이 시점 이후 노드 추가/제거 불가
executor.spin();

정적 단일 스레드 실행자의 제약 조건은, spin() 호출 이후에 노드나 엔티티를 동적으로 추가하거나 제거할 수 없다는 점이다. 이는 런타임 중 구성 변경이 빈번한 시스템에는 적합하지 않다.

5. 이벤트 기반 실행자(EventsExecutor)

ROS2 Iron 이후 실험적으로 도입된 EventsExecutor는 대기 집합 기반의 폴링 방식을 탈피하고, DDS 미들웨어로부터 이벤트를 직접 수신하는 구동 방식을 채택한다. 미들웨어 계층에서 콜백을 직접 이벤트 큐(event queue)에 인입(enqueue)하므로, 대기 집합의 구성 및 재구성 비용이 완전히 제거된다.

이벤트 기반 실행자는 다음의 성능적 이점을 제공한다.

  1. 스케줄링 지연 감소: 대기 집합 재구성이 없으므로 이벤트 발생에서 콜백 실행까지의 지연이 최소화된다.
  2. 확장성 향상: 등록된 엔티티 수에 대한 스케줄링 오버헤드가 O(1)이다.
  3. CPU 효율성: 불필요한 폴링이 제거되어 유휴(idle) 상태에서의 CPU 사용률이 감소한다.

그러나 이벤트 기반 실행자는 모든 DDS 미들웨어 구현체가 해당 인터페이스를 지원하여야 하므로, 미들웨어 호환성에 대한 제약이 존재한다.

6. 실행자 유형 간 정량적 비교

다음의 표는 각 실행자 유형의 핵심 특성을 정량적으로 비교한 것이다.

특성SingleThreadedMultiThreadedStaticSingleThreadedEventsExecutor
스레드 수1사용자 지정 (기본: 코어 수)11 또는 사용자 지정
대기 집합 재구성매 주기매 주기최초 1회불필요
콜백 병렬 실행불가가능 (그룹 정책에 따름)불가그룹 정책에 따름
동적 노드 추가가능가능불가가능
교착 상태 위험낮음높음 (그룹 설계 의존)낮음설계 의존
스케줄링 오버헤드O(n)O(n)O(1)O(1)
결정론성높음낮음높음중간

7. 로봇 행동 제어 시스템에서의 실행자 선택 지침

로봇 행동 제어 시스템에서 실행자 유형의 선택은 시스템의 동시성 요구, 실시간 제약, 공유 자원의 구조에 의해 결정된다. 다음의 지침이 권장된다.

  1. 단순 제어 루프: 단일 센서로부터 데이터를 수신하여 단일 액추에이터를 제어하는 시스템에서는 단일 스레드 실행자가 적합하다. 동기화 오류의 가능성이 배제되며 디버깅이 용이하다.

  2. 다중 센서 융합 시스템: 복수의 센서 데이터를 병렬로 처리한 후 행동 결정에 반영하는 시스템에서는 멀티 스레드 실행자가 적합하다. 각 센서 콜백을 재진입 그룹에 배치하고, 행동 결정 콜백을 상호 배타적 그룹에 배치하는 패턴이 권장된다.

  3. 고정 구성의 실시간 시스템: 런타임 중 노드 구성이 변경되지 않는 실시간 로봇 시스템에서는 정적 단일 스레드 실행자가 적합하다. 스케줄링 오버헤드의 제거를 통해 결정론적 실행이 가능하다.

  4. 대규모 분산 시스템: 수십 개 이상의 노드와 수백 개의 콜백이 등록된 대규모 시스템에서는 이벤트 기반 실행자의 확장성이 유리하다.

8. 참고 문헌

  • Open Robotics, “ROS 2 Executors,” ROS 2 Documentation, https://docs.ros.org/en/rolling/Concepts/Intermediate/About-Executors.html.
  • D. Casini, T. Blaß, I. Lütkebohle, B. B. Brandenburg, “Response-Time Analysis of ROS 2 Processing Chains Under Reservation-Based Scheduling,” in Proceedings of the 31st Euromicro Conference on Real-Time Systems (ECRTS), 2019.
  • S. Macenski, T. Foote, B. Gerkey, C. Lalancette, W. Woodall, “Robot Operating System 2: Design, architecture, and uses in the wild,” Science Robotics, vol. 7, no. 66, eabm6074, 2022.
  • Open Robotics, “Using Callback Groups,” ROS 2 Tutorials, https://docs.ros.org/en/rolling/How-To-Guides/Using-callback-groups.html.
  • iRobot, “Events Executor,” ROS 2 Design, https://design.ros2.org/articles/events_executor.html.