멀티스레딩 개념

멀티스레딩은 하나의 프로세스 내에서 여러 개의 스레드를 실행하여 작업을 병렬로 처리하는 기법이다. ROS2는 rclcpp 라이브러리를 통해 멀티스레딩을 지원하며, 이를 활용하면 퍼블리셔, 서브스크라이버, 서비스 등 여러 작업을 동시에 처리할 수 있다.

ROS2에서의 멀티스레딩 구현

ROS2에서 멀티스레딩을 구현하기 위해서는 MultiThreadedExecutor 클래스를 사용할 수 있다. 이는 여러 노드가 동시에 실행될 때 각 노드의 콜백 함수가 병렬로 처리되도록 지원한다.

실행 흐름

멀티스레딩 환경에서는 여러 스레드가 동시에 실행되므로, 각 스레드가 처리하는 작업의 상태를 주의 깊게 관리해야 한다. 일반적으로 다음과 같은 흐름을 따른다.

  1. ROS2 노드 생성
  2. 퍼블리셔 또는 서브스크라이버 등록
  3. MultiThreadedExecutor 생성
  4. 실행 중 콜백 함수가 병렬로 호출됨
rclcpp::executors::MultiThreadedExecutor executor;
executor.add_node(node);
executor.spin();

ROS2에서의 동기화 문제

멀티스레딩 환경에서 동기화 문제가 발생할 수 있다. 여러 스레드가 동시에 자원을 접근하거나 수정하는 상황을 피하기 위해 뮤텍스(Mutex) 를 활용할 수 있다.

동기화 수식

멀티스레딩에서 자원을 보호하기 위한 수학적 모델은 다음과 같이 표현할 수 있다.

\mathbf{R}_{\text{shared}} = f(\mathbf{T}_{1}, \mathbf{T}_{2}, \dots, \mathbf{T}_{n})

여기서,
- \mathbf{R}_{\text{shared}}는 공유 자원이다.
- \mathbf{T}_{i}는 각 스레드 i에 의해 처리되는 작업이다.

모든 \mathbf{T}_{i}가 동시에 \mathbf{R}_{\text{shared}}에 접근할 경우 동기화 문제가 발생할 수 있다. 따라서, 동기화를 위해 뮤텍스가 사용되며, 뮤텍스는 각 스레드가 자원에 접근할 때 잠금(Lock)과 해제(Unlock)를 적용한다.

std::mutex mtx;
mtx.lock();
// 공유 자원에 접근하는 코드
mtx.unlock();

뮤텍스를 사용하여 각 스레드는 자원 접근 전에 자원을 잠그고, 작업을 마치면 잠금을 해제하게 된다.

멀티스레딩과 콜백 그룹

ROS2의 멀티스레딩을 보다 효과적으로 관리하기 위해 콜백 그룹(Callback Group) 을 사용할 수 있다. 콜백 그룹은 노드 내에서 특정 콜백 함수들을 하나의 그룹으로 묶어 해당 그룹 내의 콜백들이 동일한 스레드에서만 실행되도록 설정할 수 있다. 이를 통해 특정 작업 간의 동기화를 보다 쉽게 관리할 수 있다.

콜백 그룹의 사용

콜백 그룹은 rclcpp::CallbackGroup 클래스를 사용하여 정의된다. 콜백 그룹을 생성하고, 각 콜백 함수를 해당 그룹에 추가하여 관리한다. 이를 통해 특정 콜백 그룹이 다른 그룹과 병렬로 실행될 수 있다.

콜백 그룹의 정의 및 사용 예시

auto callback_group = node->create_callback_group(rclcpp::CallbackGroupType::MutuallyExclusive);
auto subscription_options = rclcpp::SubscriptionOptions();
subscription_options.callback_group = callback_group;

위의 예제에서 MutuallyExclusive 타입의 콜백 그룹을 생성했으며, 이 그룹에 속한 콜백 함수들은 동일한 스레드에서만 실행된다. 콜백 그룹을 설정하면, 특정 작업 간에 필요한 동기화가 더 간단해진다.

멀티스레딩 시 발생할 수 있는 문제

멀티스레딩을 사용할 때 발생할 수 있는 몇 가지 주요 문제는 다음과 같다.

데드락(Deadlock)

데드락은 두 개 이상의 스레드가 서로 다른 자원의 잠금을 대기하면서 영원히 멈추는 상황을 말한다. 이를 방지하기 위해 각 스레드가 자원에 접근할 순서를 정하거나, 뮤텍스를 사용할 때 잠금 순서를 신중하게 관리해야 한다.

데드락이 발생할 수 있는 조건을 수학적으로 표현하면:

\mathbf{T}_{i} \text{는 } \mathbf{R}_{i} \text{를 대기 중}, \mathbf{T}_{j} \text{는 } \mathbf{R}_{j} \text{를 대기 중일 때, 교차적으로 } \mathbf{R}_{i}, \mathbf{R}_{j} \text{를 대기 중이면 데드락 발생}

라이브락(Livelock)

라이브락은 데드락과 유사하지만, 각 스레드가 자원을 양보하려고 노력하는 과정에서 작업이 완료되지 않고 계속해서 상태가 변하는 현상을 말한다. 이를 방지하기 위해서는 스레드가 자원을 효율적으로 양보할 수 있는 메커니즘을 도입해야 한다.

멀티프로세싱 개념

멀티프로세싱은 멀티스레딩과는 다르게, 각 작업을 별도의 프로세스에서 실행하는 기법이다. ROS2에서는 각 노드를 개별 프로세스로 실행할 수 있으며, 이를 통해 시스템의 안정성을 높이고, 병렬 처리를 더욱 효율적으로 할 수 있다. 멀티프로세싱은 각 프로세스가 독립된 메모리 공간을 가지기 때문에 스레드 간의 동기화 문제를 피할 수 있다는 장점이 있다.

ROS2와 멀티프로세싱

ROS2에서는 기본적으로 각 노드가 독립적인 프로세스로 실행된다. 하지만 멀티프로세싱을 더 적극적으로 활용하기 위해서는 런치 시스템을 사용하여 여러 노드를 병렬로 실행할 수 있다.

런치 시스템을 통한 멀티프로세싱 설정

ROS2의 런치 파일을 사용하면 여러 개의 노드를 동시에 실행할 수 있다. 이를 통해 여러 노드를 병렬로 처리하여 멀티프로세싱의 장점을 최대한 활용할 수 있다.

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_package',
            executable='node1',
            name='node1',
            output='screen'),
        Node(
            package='my_package',
            executable='node2',
            name='node2',
            output='screen')
    ])

위의 예시에서는 두 개의 노드 node1node2를 동시에 실행한다. 이때 각 노드는 독립된 프로세스로 실행되며, 서로 다른 작업을 병렬로 처리할 수 있다.

멀티프로세싱의 데이터 공유

멀티프로세싱의 단점 중 하나는 각 프로세스가 독립된 메모리 공간을 사용하기 때문에 데이터를 공유하기 어렵다는 점이다. 이를 해결하기 위해 ROS2는 인터프로세스 통신(IPC, Inter-Process Communication) 을 제공한다. ROS2에서는 퍼블리셔와 서브스크라이버를 통해 각 노드 간 데이터를 주고받을 수 있다.

\mathbf{P}_{1} \xrightarrow{\text{publish}} \mathbf{T}_{1} \quad \mathbf{P}_{2} \xrightarrow{\text{subscribe}} \mathbf{T}_{2}

여기서,
- \mathbf{P}_{1}, \mathbf{P}_{2}는 서로 다른 프로세스에서 실행되는 퍼블리셔와 서브스크라이버를 의미한다.
- \mathbf{T}_{1}, \mathbf{T}_{2}는 각 프로세스의 작업이다.

멀티프로세싱에서의 자원 관리

멀티프로세싱에서는 각 프로세스가 독립적으로 실행되므로 메모리 자원이나 CPU 자원을 적절히 분배하는 것이 중요하다. ROS2 런치 시스템을 활용하여 각 노드의 자원을 효율적으로 관리할 수 있으며, 이를 통해 시스템 성능을 극대화할 수 있다.

멀티프로세싱과 ROS2 런치 시스템의 응용

ROS2 런치 시스템은 멀티프로세싱의 강력한 도구로, 복잡한 시스템에서 여러 노드를 병렬로 실행하고, 각 노드의 파라미터를 관리하는 데 효과적이다. 런치 파일을 활용하면 노드 간의 의존성을 정의하거나, 실행 순서를 제어하고, 필요에 따라 노드를 동적으로 실행 및 중지할 수 있다.

런치 파일에서 조건부 실행

복잡한 ROS2 시스템에서는 특정 조건이 충족될 때만 노드를 실행해야 하는 경우가 있다. 이를 위해 ROS2 런치 파일은 조건부 실행을 지원한다. 예를 들어, 노드가 특정 파라미터의 값에 따라 실행 여부를 결정할 수 있다.

from launch import LaunchDescription
from launch.conditions import IfCondition
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_package',
            executable='node1',
            name='node1',
            output='screen',
            condition=IfCondition('true')),
        Node(
            package='my_package',
            executable='node2',
            name='node2',
            output='screen',
            condition=IfCondition('false'))
    ])

위 예시에서는 IfCondition을 사용하여 특정 조건에 따라 노드가 실행될 수 있도록 설정하였다. node1은 조건이 true일 때 실행되고, node2는 조건이 false일 때 실행되지 않는다. 이를 통해 시스템의 동적 실행을 제어할 수 있다.

런치 파일을 통한 멀티프로세스 병렬 실행

런치 파일을 사용하면 여러 노드를 병렬로 실행하여 멀티프로세싱을 쉽게 구현할 수 있다. ROS2에서는 이를 통해 복잡한 로봇 시스템을 관리할 수 있다.

멀티프로세싱에서 각 프로세스 간의 자원 분배 및 동기화는 매우 중요한 문제이다. ROS2는 DDS(Data Distribution Service)를 기반으로 하여 노드 간 통신을 관리하며, 퍼블리셔와 서브스크라이버를 통해 데이터를 공유하게 된다. 이를 통해 멀티프로세스 환경에서도 안정적으로 데이터를 처리할 수 있다.

멀티프로세싱과 자원 관리

멀티프로세싱을 사용할 때는 시스템의 자원(CPU, 메모리 등)을 효율적으로 분배해야 한다. 특히, 각 프로세스가 독립적인 메모리 공간을 사용하므로 메모리 사용량이 증가할 수 있으며, 이를 효율적으로 관리하는 것이 필요하다.

CPU 자원 관리

멀티프로세싱 환경에서 각 프로세스는 독립적으로 실행되므로 CPU 자원을 효율적으로 분배해야 한다. 이를 위해 ROS2는 런치 파일에서 각 프로세스에 대한 자원 제한을 설정할 수 있다. 예를 들어, cpuset 옵션을 사용하여 특정 노드가 특정 CPU 코어에서만 실행되도록 설정할 수 있다.

from launch import LaunchDescription
from launch_ros.actions import Node

def generate_launch_description():
    return LaunchDescription([
        Node(
            package='my_package',
            executable='node1',
            name='node1',
            output='screen',
            parameters=[{'use_sim_time': True}],
            remappings=[('/cmd_vel', '/robot1/cmd_vel')],
            additional_env={'CPUSETS': '1'}),
        Node(
            package='my_package',
            executable='node2',
            name='node2',
            output='screen',
            additional_env={'CPUSETS': '2'})
    ])

이 예시에서는 node1이 CPU 1에서 실행되도록, node2는 CPU 2에서 실행되도록 설정하였다. 이렇게 하면 각 프로세스가 독립적으로 자원을 사용하면서 충돌을 피할 수 있다.

메모리 자원 관리

멀티프로세싱 환경에서 각 프로세스가 독립적으로 메모리를 사용하므로, 메모리 관리 또한 매우 중요하다. 메모리 자원이 부족하면 시스템의 성능이 저하되거나, 프로세스가 비정상적으로 종료될 수 있다. 이를 방지하기 위해 각 노드에서 사용하는 메모리를 모니터링하고, 필요시 메모리 사용량을 최적화할 수 있다.

멀티프로세싱에서의 상호작용

멀티프로세싱을 사용할 때 각 프로세스 간의 상호작용이 매우 중요하다. ROS2에서는 퍼블리셔-서브스크라이버 패턴을 통해 데이터를 교환하며, 서비스와 액션을 통해 비동기 작업을 처리한다.

멀티프로세싱 환경에서 각 프로세스 간 상호작용을 효과적으로 관리하기 위해서는 통신의 지연(latency)을 최소화하고, 데이터 전송의 일관성을 유지해야 한다. 이를 위해 ROS2는 DDS 프로토콜을 활용하여 데이터의 일관성, 신뢰성, 순서를 보장한다.