Skip to content

1.3 스레드 모델

핵심 의미

1. 동기 호출

  • Connect() , Read() , MoveJoint() 등의 인터페이스는 호출자 관점에서 모두 동기적으로 반환됩니다.
  • 호출 스레드는 요청 완료를 기다린 뒤 STATUS_CODE 또는 조회 결과를 받습니다.
  • 같은 Arm 인스턴스에서 동시에 시작한 네트워크 호출은 내부에서 제출 순서대로 직렬 실행됩니다.

2. 주기 작업

  • ControllerInfo::AcquireAccess()2000ms keep-alive 타이머 작업을 등록합니다.
  • JoggingControl::ContinuousMove() / MultiMove()50ms 연속 조그 타이머 작업을 등록합니다.
  • 이러한 주기 작업은 Arm 세션의 전용 네트워크 스레드를 재사용합니다.
  • Arm 세션을 중지하면 관련 타이머 작업도 함께 정리됩니다.

3. 구독/발행(SubPub)

  • TopicPubSub 의 수신 패킷은 WebSocket 라이브러리 콜백을 통해 SDK로 들어옵니다. 이 페이지에서는 WebSocket 라이브러리 자체 스레드는 다루지 않습니다.
  • SDK는 SubPubService 내부에 messages 메시지 큐를 유지하며, Receive() 는 큐에서 다음 메시지를 동기적으로 가져옵니다.
  • 메시지 큐의 상한은 100 개이며, 가득 차면 가장 오래된 메시지를 버립니다.
  • 여러 Arm / TopicPubSub 인스턴스는 각자의 WebSocket 연결을 동시에 만들 수 있습니다. 그중 하나를 파괴하거나 연결 해제해도 계속 살아 있는 다른 인스턴스에는 영향을 주지 않아야 합니다.

SDK 스레드 뷰

스레드 안전 규칙

Arm 인스턴스

  • 단일 스레드 접근: 각 Arm 인스턴스는 하나의 업무 스레드에서 접근해야 합니다.
  • 다중 인스턴스 동시성: 여러 로봇을 동시에 제어해야 하는 경우 여러 Arm 인스턴스를 생성합니다.
  • 스레드 안전하지 않음: 외부 동기화 메커니즘 없이 Arm 인스턴스를 여러 스레드에서 공유하지 마십시오.

서비스 모듈

모든 서비스 모듈( motionControl , controllerInfo , alarmClient 등)은 소속된 Arm 인스턴스의 스레드 안전 특성을 따릅니다.

cpp
// 안전: 모든 모듈을 단일 스레드에서 접근합니다.  // 같은 업무 스레드에서 같은 Arm을 직렬 호출합니다.
Arm arm;  // Arm 세션 객체를 생성합니다.
arm.Connect("192.168.110.2", "");  // 현재 업무 스레드에서 로봇에 연결합니다.
arm.motionControl.GetCurrentPose(PoseType::JOINT);  // 현재 관절 포즈를 직렬로 읽습니다.
arm.controllerInfo.GetCtrlStatus();  // 제어 상태를 직렬로 읽습니다.
arm.alarmClient.GetAllActiveAlarms();  // 활성 알람 목록을 직렬로 읽습니다.
// 안전하지 않음: 여러 스레드가 같은 Arm에 접근합니다.  // 아래 두 줄은 반례입니다.
// 스레드 1  // 첫 번째 스레드가 모션을 시작하려고 합니다.
arm.motionControl.MoveJoint(target1, 0.5, 0.5);  // 스레드 1이 같은 Arm의 모션 인터페이스를 호출합니다.
// 스레드 2(동시 실행)  // 두 번째 스레드가 동시에 모션을 시작합니다.
arm.motionControl.MoveJoint(target2, 0.5, 0.5);  // 정의되지 않은 동작입니다.

안전한 동시 접근 패턴

cpp
// 안전: 여러 Arm 인스턴스로 동시 접근을 구현합니다.  // 각 로봇은 독립적인 Arm 세션을 사용합니다.
Arm arm1, arm2;  // 서로 독립적인 Arm 객체 두 개를 생성합니다.
arm1.Connect("192.168.110.2", "");  // 첫 번째 로봇에 연결합니다.
arm2.Connect("192.168.110.3", "");  // 두 번째 로봇에 연결합니다.
// 스레드 1  // 첫 번째 스레드는 arm1만 사용합니다.
arm1.motion.MoveJoint(target1, 0.5, 0.5);  // 첫 번째 로봇에 모션을 전달합니다.
// 스레드 2(동시 실행, 다른 로봇)  // 두 번째 스레드는 arm2만 사용합니다.
arm2.motion.MoveJoint(target2, 0.5, 0.5);  // 안전합니다.

콜백 사용 지침

권장 사항

  • 콜백 함수는 짧고 빠르게 유지합니다.
  • 후속 처리가 필요하면 먼저 데이터를 복사합니다.
  • 스레드 안전 로그 기록을 사용합니다.
  • 다른 알림이 계속 처리될 수 있도록 빠르게 반환합니다.

금지 사항

  • 콜백 안에서 블로킹하지 마십시오.
  • 콜백 안에서 블로킹 SDK API를 호출하지 마십시오.
  • 동기화 메커니즘 없이 콜백 안에서 다른 Arm 인스턴스에 접근하지 마십시오.
  • 콜백 안에서 예외를 던지지 마십시오.

예: 올바른 콜백 패턴

cpp
// 올바름: 빠르고 블로킹하지 않는 콜백  // 콜백 스레드는 데이터 전달만 수행합니다.
void OnJointPosition(const JointPositionMsg& msg) {  // 관절 위치 메시지를 받으면 콜백에 진입합니다.
    std::lock_guard<std::mutex> lock(dataMutex_);  // 공유 캐시 보호를 위해 잠급니다.
    latestJoints_ = msg.joints;  // 메시지 데이터를 빠르게 복사합니다.
}  // 즉시 반환해 수신 스레드가 막히지 않게 합니다.
void ProcessData() {  // 업무 스레드에서 캐시된 데이터를 처리합니다.
    std::vector<Float64> joints;  // 업무 스레드 자체 데이터 사본을 준비합니다.
    {  // 잠금 범위를 줄입니다.
        std::lock_guard<std::mutex> lock(dataMutex_);  // 공유 캐시 읽기를 위해 잠급니다.
        joints = latestJoints_;  // 최신 관절 데이터를 로컬 변수로 복사합니다.
    }  // 잠금을 해제해 콜백 스레드가 계속 캐시를 업데이트할 수 있게 합니다.
    // 데이터를 안전하게 처리합니다.  // 시간이 걸리는 업무 로직은 잠금 밖에서 실행합니다.
}  // 업무 스레드 처리 함수를 종료합니다.

예: 잘못된 콜백 패턴

cpp
// 잘못됨: 콜백 안에서 블로킹합니다.
void OnAlarm(const AlarmMsg& msg) {  // 알람 메시지를 받으면 콜백에 진입합니다.
    arm.alarmClient.Reset();  // 콜백 안에서 블로킹 SDK API를 호출하면 안 됩니다. 데드락 위험이 있습니다.
    ProcessLargeData(msg);  // 콜백 안에서 시간이 걸리는 처리를 수행하면 안 됩니다. 다른 콜백을 막습니다.
}  // 잘못된 예제 콜백을 종료합니다.

권장 사용 방식

  • 같은 ArmConnect() / Disconnect() / 모드 전환은 동일한 업무 스레드 또는 동일한 스케줄링 로직에서 통합 관리합니다.
  • TopicPubSub 콜백에서는 핵심 필드 파싱, 큐에 전달, 플래그 설정 같은 가벼운 작업만 수행합니다.
  • 시간이 걸리는 로직, 2차 제어 명령, 재연결 동작은 업무 스레드에서 실행하고 콜백 안에서 직접 막지 마십시오.

사용 제약

금지되는 재진입 작업

TopicPubSub 콜백 안에서 다음 항목을 직접 호출하지 마십시오.

  • Arm::Connect()Arm::Disconnect()
  • TopicPubSub::Connect()TopicPubSub::Disconnect()
  • StartReceiving()RemoveMessageHandler()

이러한 재진입 작업은 OTHER_ERR 를 반환하거나 무시될 수 있습니다.

기타 제약

  • 서로 다른 인터페이스 모듈이 같은 로봇에 병렬로 요청한다고 가정하지 마십시오. 같은 Arm 의 요청은 기본적으로 직렬 스케줄링된다고 이해하는 것이 더 안전합니다.
  • 업무에서 높은 처리량이나 장치 격리가 필요하면 여러 Arm / ArmHandle* 세션을 사용하십시오.

C99 설명

  • Arm_Destroy() 전에 먼저 Arm_Disconnect() 를 호출해 해당 세션의 네트워크 스레드와 타이머 작업이 정상적으로 정리되도록 해야 합니다.

성능 고려 사항

연결 복구

네트워크 스레드가 연결 끊김을 감지한 경우:

  • 연결 복구는 호출자가 Disconnect() 를 호출한 뒤 다시 Connect() 를 호출해 수행합니다.
  • 모든 대기 중인 작업은 오류 코드를 반환합니다.

모범 사례 요약

  1. 데이터 복사: 콜백에서 데이터를 복사하고 작업 스레드에서 처리합니다.
  2. 오류 처리: 항상 반환 코드를 확인하고 연결 끊김을 적절히 처리합니다.
  3. 리소스 정리: Arm 인스턴스를 파괴하기 전에 반드시 Disconnect() 를 호출합니다.