ROS 2 Executor는 구독·타이머·서비스 콜백을 운영체제 스레드에서 실행하고, Callback Group은 어떤 콜백이 병렬로 실행될 수 있는지 정합니다. Multi-Threaded Executor를 선택하는 것만으로 병렬성이 생기지 않으며, 기본 그룹·블로킹 호출·공유 잠금이 제어 주기를 막는지 측정해야 합니다.
아래에서는 용어의 차이를 실제 시스템 경계, 실패 조건과 검증 순서로 나눠 살펴봅니다.
Executor는 메시지를 처리하는 추상화가 아니라 콜백 실행 관리자다
ROS 2의 구독, 타이머, 서비스, 액션은 이벤트가 준비됐을 때 콜백을 실행합니다. ROS 2 실시간 제어 글이 전체 루프의 시간 경계를 설명했다면, 이 글은 콜백이 어느 스레드에서 언제 실행되는지가 그 경계를 흔드는 과정을 다룹니다.
Executor는 준비된 이벤트를 기다리고 실행 가능한 콜백을 선택해 하나 이상의 스레드에 배정합니다. 센서 데이터가 이미 도착했는데도 콜백 시작이 늦다면 네트워크가 아니라 executor 대기·그룹 제약·공유 잠금이 원인일 수 있습니다.
Single-Threaded Executor에서는 긴 콜백 하나가 모두를 기다리게 한다
ROS 2 Executor 공식 문서는 Single-Threaded Executor가 한 스레드에서 여러 노드의 콜백을 처리한다고 설명합니다. 이미지 처리나 동기 서비스 호출이 길어지면 같은 executor의 제어 타이머도 그 시간만큼 시작을 기다립니다.
코드가 스레드 안전하다는 장점은 있지만 실행시간 상한이 다른 작업을 한 줄에 세우는 결과가 됩니다. 제어 주기보다 오래 걸릴 수 있는 파일 I/O, 모델 추론, 압축과 원격 서비스는 같은 단일 스레드 경로에 두지 않는 편이 좋습니다.
Multi-Threaded Executor도 기본 Callback Group 하나면 직렬 실행될 수 있다
Multi-Threaded Executor는 여러 worker thread를 가질 수 있지만, 콜백이 같은 Mutually Exclusive 그룹에 있으면 동시에 실행되지 않습니다. 노드에서 그룹을 따로 지정하지 않은 콜백은 기본 그룹에 들어가므로 스레드 수만 늘리고 효과가 없는 경우가 생깁니다.
반대로 그룹을 무조건 쪼개면 공유 상태에 동시 접근해 경쟁 조건이 생길 수 있습니다. 병렬화 목표, 공유 데이터 소유권과 재진입 가능성을 먼저 정하고 그 결과로 그룹 유형과 스레드 수를 선택해야 합니다.
| 구성 | 병렬 가능성 | 대표 위험 |
|---|---|---|
| Single + 기본 그룹 | 없음 | 긴 콜백의 전체 차단 |
| Multi + 기본 그룹 | 대부분 제한 | 스레드 추가 효과 없음 |
| Multi + 복수 배타 그룹 | 그룹 사이 가능 | 공유 mutex 경합 |
| Multi + Reentrant | 동일 콜백도 가능 | 비재진입 코드 충돌 |
Mutually Exclusive 그룹은 그룹 안 콜백의 동시 실행을 막는다
Callback Group 사용 가이드는 Mutually Exclusive와 Reentrant의 실행 제약을 구분합니다. 배타 그룹은 같은 그룹의 콜백이 동시에 실행되지 않으므로 공유 상태를 단순하게 보호할 수 있습니다.
하지만 고주기 타이머와 오래 걸리는 서비스 콜백을 같은 배타 그룹에 넣으면 서비스가 끝날 때까지 제어 콜백이 시작되지 않습니다. 보호해야 할 상태가 아니라 실행시간 특성이 다른 작업까지 한 그룹으로 묶지 않았는지 확인해야 합니다.
Reentrant 그룹은 병렬성을 허용하지만 스레드 안전성을 보장하지 않는다
Reentrant 그룹의 콜백은 서로뿐 아니라 같은 콜백의 여러 인스턴스도 겹쳐 실행될 수 있습니다. 네트워크 응답이나 독립 프레임 처리처럼 상태가 분리된 작업에는 유용하지만, 전역 버퍼나 비재진입 라이브러리를 공유하면 데이터 손상이 생길 수 있습니다.
Reentrant를 선택할 때는 콜백 입력별 상태 분리, 불변 객체, bounded queue와 명시적 소유권을 준비합니다. mutex 하나로 모든 실행을 다시 직렬화하면 그룹을 바꾼 이점이 사라지고 우선순위 역전 가능성만 남습니다.
동기 서비스 호출과 Future 대기는 자기 자신을 막을 수 있다
콜백 안에서 동기 서비스 응답을 기다리는데 응답 완료 콜백이 같은 배타 그룹과 executor에서 실행돼야 하면 교착 또는 장시간 정지가 생길 수 있습니다. 단순 네트워크 지연처럼 보여도 실행 구조 때문에 응답 콜백이 기회를 얻지 못한 것입니다.
가능하면 비동기 호출로 바꾸고 완료 콜백을 실행 가능한 그룹에 두며 타임아웃과 취소 경로를 둡니다. 동기 호출이 필요하면 호출·응답의 그룹과 executor 스레드 관계를 그려 실제로 진행 가능한 경로인지 검토합니다.
스레드 수보다 콜백 실행시간과 대기시간 분포가 먼저다
카메라부터 명령까지의 전체 지연은 로봇 추론 지연 예산 글처럼 구간별로 나눠야 합니다. Executor에서는 이벤트 ready 시점, 콜백 시작, 종료를 기록해 큐 대기와 실제 계산 시간을 구분합니다.
CPU 사용률이 낮아도 하나의 배타 그룹이나 mutex에서 대기할 수 있고, 평균 실행시간이 짧아도 드문 긴 꼬리가 제어 deadline을 넘길 수 있습니다. P50·P95·P99·최대와 연속 miss 횟수를 함께 봐야 현장 간헐 오류를 찾습니다.
우선순위는 executor 선택만으로 자동 보장되지 않는다
제어 콜백을 별도 callback group과 executor에 배치하고 운영체제 스레드 우선순위를 다르게 설정할 수 있습니다. 그러나 낮은 우선순위 콜백이 잡은 mutex를 높은 우선순위 제어 콜백이 기다리면 우선순위 역전이 생깁니다.
고주기 경로에서는 공유 잠금 범위를 줄이고 메모리 할당과 로그 I/O를 분리하며, 우선순위 상속이 필요한지 검토합니다. 스레드 affinity와 우선순위 설정은 실제 운영 커널과 권한에서 적용됐는지 측정으로 확인해야 합니다.
QoS와 Executor 지연은 다른 층이지만 함께 증상으로 나타난다
ROS 2 QoS 설정 글은 전달 호환성과 큐·신선도를 다룹니다. 메시지가 전달되지 않은 것과 전달됐지만 콜백이 늦게 실행된 것은 원인이 다르므로 수신 시각, ready 시각과 콜백 시작 시각을 분리해야 합니다.
Reliable 재전송과 깊은 queue가 처리량을 유지하는 대신 오래된 콜백을 쌓을 수 있고, Best Effort는 손실을 허용해 최신값을 우선할 수 있습니다. QoS 변경으로 executor 병목을 숨기지 말고 작업 의도와 지연 예산을 함께 맞춰야 합니다.

진단은 추적·부하 주입·그룹 변경을 단계적으로 수행한다
먼저 콜백별 ready·start·end와 thread ID, group ID를 추적합니다. 다음으로 긴 센서 처리, 서비스 지연, 로그 폭주를 하나씩 주입해 제어 타이머의 대기시간과 deadline miss가 어떻게 변하는지 봅니다.
그 뒤 긴 작업을 다른 그룹이나 executor로 옮기고 동일 부하를 반복합니다. 개선이 없다면 공유 mutex, CPU 포화, 메모리와 I/O 대기까지 범위를 넓혀야 하며 스레드 수만 반복해서 늘리는 접근은 원인 구분에 도움이 되지 않습니다.
| 단계 | 기록 | 판단 |
|---|---|---|
| 1. 기준 | ready·start·end | 실행과 대기 분리 |
| 2. 부하 | 센서·서비스·I/O 주입 | 간섭 경로 식별 |
| 3. 분리 | 그룹·executor 변경 | deadline 회복 확인 |
| 4. 경계 | CPU·mutex·메모리 | 남은 꼬리 원인 확인 |
실시간 경로와 편의 기능은 실행 자원도 분리한다
ROS 2 실시간 프로그래밍 데모는 페이지 폴트와 동적 메모리처럼 결정성을 해치는 요소를 다룹니다. 제어 타이머를 분리해도 같은 프로세스의 할당·로깅·잠금이 경로를 침범하면 지연 꼬리가 남습니다.
운영 UI, 진단 업로드, 파일 저장과 무거운 추론은 낮은 우선순위 실행 경로로 보내고 제어 경로는 bounded 작업만 수행하게 합니다. 분리 뒤에도 필요한 상태 전달은 lock-free 또는 짧고 예측 가능한 교환 구조로 설계합니다.
인수 기준은 처리량보다 deadline과 데이터 신선도로 정한다
Executor 튜닝의 목표를 초당 콜백 수로만 두면 오래된 센서 데이터를 빠르게 처리하는 시스템이 될 수 있습니다. 제어 타이머 지터, 센서 age, deadline miss, queue backlog와 폐기 정책을 작업 위험에 맞춰 정합니다.
센서 퓨전의 시간 정렬과 함께 측정하면 콜백 간섭이 추정 오차로 전파되는 지점을 찾을 수 있습니다. 정상 부하뿐 아니라 서비스 폭주와 센서 버스트에서도 같은 기준을 통과해야 배치 구성이 완료됩니다.

ROS 2 Executor와 Callback Group에서 자주 묻는 질문
Multi-Threaded Executor를 쓰면 모든 콜백이 병렬로 실행되나요?
아닙니다. Callback Group 유형과 배치가 병렬 가능성을 제한하고 공유 mutex와 CPU 자원도 실제 동시 실행을 막을 수 있습니다.
기본 Callback Group을 그대로 써도 되나요?
짧고 비차단 콜백만 있는 단순 노드에는 가능하지만, 제어 타이머와 긴 서비스·센서 처리가 섞이면 의도적으로 분리하는 편이 좋습니다.
Reentrant 그룹이 더 빠른가요?
병렬 실행 기회를 늘릴 수 있지만 상태 분리와 스레드 안전성이 필요합니다. 공유 잠금으로 다시 직렬화하면 이점이 줄어듭니다.
QoS를 Best Effort로 바꾸면 Executor 지연이 해결되나요?
일부 backlog를 줄일 수 있지만 콜백 실행과 잠금 병목은 남습니다. 전달 손실과 실행 대기를 별도로 측정해야 합니다.
가장 먼저 기록할 지표는 무엇인가요?
콜백의 ready·start·end 시각, thread와 group 식별자, 제어 deadline과 센서 age를 함께 기록하면 대기와 실행 원인을 나눌 수 있습니다.
Executor 동작과 API는 ROS 2 배포판 및 rclcpp·rclpy에 따라 차이가 있습니다. 실제 구성은 사용 중인 배포판 문서와 운영체제 스케줄링 조건에서 재검증해야 합니다.