1293.32 Parallel 노드의 동시 자식 Tick
1. 동시 자식 Tick의 의미
Parallel 노드에서의 동시 자식 Tick이란, 단일 Tick 내에서 모든 자식 노드가 Tick을 수신하여 실행되는 것을 의미한다. Sequence나 Fallback 노드가 특정 자식의 반환 상태에 따라 후속 자식의 Tick을 생략(조기 종료)하는 것과 달리, Parallel 노드는 원칙적으로 모든 자식에 Tick을 전파한다. 이를 통해 복수의 행동이 논리적으로 동시에 진행되는 것을 모델링한다(Colledanchise & Ogren, 2018).
2. 순차적 실행과 논리적 동시성
Parallel 노드의 “동시” Tick은 실제로는 순차적으로 수행된다. 단일 Tick 내에서 자식 노드의 tick() 메서드가 왼쪽에서 오른쪽 순서로 하나씩 호출되며, 하나의 자식의 tick()이 완료된 후에야 다음 자식의 tick()이 호출된다.
Parallel Tick 실행 순서 (단일 스레드):
t₀: Child_A.tick() 호출 및 완료
t₁: Child_B.tick() 호출 및 완료
t₂: Child_C.tick() 호출 및 완료
t₃: 성공/실패 카운트 집계 및 반환 상태 결정
이 순차적 실행에도 불구하고 “동시“라 표현하는 이유는, 모든 자식이 동일한 Tick 주기 내에서 각각 한 번씩 실행 기회를 부여받기 때문이다. 각 비동기 자식 노드는 별도의 외부 작업(ROS2 액션, 스레드 기반 연산 등)을 수행하며, tick() 호출 시에는 해당 작업의 진행 상태만 확인하므로, 실질적으로 복수의 작업이 병렬로 진행되는 효과를 달성한다.
3. 동시 Tick에서의 실행 순서 영향
자식 노드가 순차적으로 Tick되므로, 왼쪽 자식의 실행이 오른쪽 자식의 실행에 영향을 미칠 수 있다. 특히 블랙보드를 통해 데이터를 공유하는 경우, 앞선 자식이 블랙보드에 기록한 값을 뒤따르는 자식이 즉시 읽을 수 있다.
Parallel
├── Child_A: 블랙보드에 "distance" = 3.5 기록
├── Child_B: 블랙보드에서 "distance" 읽기 → 3.5 (Child_A가 기록한 값)
└── Child_C: 블랙보드에서 "distance" 읽기 → 3.5
이러한 순서 의존성은 의도된 것일 수도, 의도치 않은 것일 수도 있다. 자식 간의 데이터 의존성이 존재하는 경우 자식의 배치 순서를 신중히 결정해야 하며, 순서에 무관한 동작을 보장하려면 Tick 시작 시 스냅샷을 취하는 기법을 적용해야 한다.
4. 이미 완료된 자식의 처리
Parallel 노드에서 이미 SUCCESS 또는 FAILURE를 반환한 자식은 후속 Tick에서 재Tick되지 않는다. Parallel 노드는 각 자식의 이전 반환 상태를 기억하고, 아직 완료되지 않은(RUNNING 상태인) 자식에 대해서만 Tick을 전파한다.
Tick 1: Parallel → A(RUNNING), B(SUCCESS), C(RUNNING)
3개 자식 모두 Tick
Tick 2: Parallel → A(Tick), B(건너뜀), C(Tick)
B는 이미 SUCCESS이므로 재Tick하지 않음
B의 상태는 SUCCESS로 유지
이 동작은 BehaviorTree.CPP에서 각 자식의 완료 여부를 추적하는 내부 플래그를 통해 구현된다.
5. 동시 Tick의 시간 비용
Parallel 노드의 단일 Tick 실행 시간은 모든 자식의 Tick 실행 시간의 합에 근사한다.
T_{parallel} \approx \sum_{i \in \text{active}} T_{child_i}
여기서 “active“는 아직 RUNNING 상태인 자식의 집합이다. 자식 수가 많거나 개별 자식의 Tick 실행 시간이 긴 경우, Parallel 노드의 총 Tick 실행 시간이 Tick 주기를 초과하여 오버런이 발생할 수 있다.
이를 관리하기 위해 Parallel 노드의 자식은 가능한 한 경량의 onRunning() 구현을 가져야 하며, 무거운 연산은 별도의 스레드에서 수행하도록 분리해야 한다.
6. 동시 Tick과 비동기 노드의 조합
Parallel 노드의 진정한 병렬 실행 효과는 비동기 노드와의 조합을 통해 달성된다. 각 자식이 StatefulActionNode로 구현되어 onStart()에서 비동기 작업을 개시하고, onRunning()에서는 완료 여부만 확인하면, 복수의 외부 작업이 실제로 병렬로 진행된다.
// Child_A: 네비게이션 실행
BT::NodeStatus onStart() override {
nav_client_->send_goal(goal_a_);
return BT::NodeStatus::RUNNING;
}
// Child_B: 팔 동작 실행 (동시에)
BT::NodeStatus onStart() override {
arm_client_->send_goal(arm_pose_);
return BT::NodeStatus::RUNNING;
}
두 작업은 서로 다른 ROS2 액션 서버에서 독립적으로 실행되며, 매 Tick에서 각각의 진행 상태가 확인된다.
7. 동시 Tick과 자원 경합
복수의 자식이 동일한 물리적 자원(예: 동일한 모터, 동일한 센서)에 접근하는 경우, 자원 경합(resource contention)이 발생할 수 있다. Parallel 노드는 논리적 동시성을 제공하지만, 물리적 자원의 배타적 사용이 필요한 경우에는 적절한 자원 관리 메커니즘(블랙보드 기반 잠금, 뮤텍스 등)이 필요하다.
예를 들어, “전진 이동“과 “회전“을 동시에 수행하는 것은 물리적으로 가능하지만, “왼쪽 회전“과 “오른쪽 회전“을 동시에 수행하는 것은 물리적으로 모순이다. 이러한 상호 배타적 행동을 Parallel 노드의 자식으로 배치하지 않도록 설계 시 주의해야 한다.
8. 동시 Tick의 적용 사례
| 사례 | 동시 수행 내용 | 근거 |
|---|---|---|
| 이동 + 감시 | 목표로 이동하면서 장애물 감시 | 이동과 감시는 독립적 |
| 팔 동작 + 이동 | 물체를 들어올리면서 이동 | 상체와 하체의 독립 제어 |
| 복수 센서 처리 | LiDAR, 카메라, IMU 동시 처리 | 센서 간 독립적 |
| 통신 + 계산 | 데이터 전송과 경로 계획 동시 수행 | I/O와 계산의 병렬화 |
참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Faconti, D. (2022). BehaviorTree.CPP documentation and API reference. https://www.behaviortree.dev/