1.3 Thread Model
Core Semantics
1. Synchronous Calls
- APIs like
Connect(),Read(), andMoveJoint()are synchronous from the caller's perspective. - The calling thread waits for the request to complete, then receives the
STATUS_CODEor query result. - Concurrent network calls on the same
Arminstance are serialized in submission order.
2. Periodic Tasks
ControllerInfo::AcquireAccess()registers a2000mskeep-alive timer task.JoggingControl::ContinuousMove()/MultiMove()register50mscontinuous-jogging timer tasks.- These periodic tasks reuse the dedicated network thread of the
Armsession. - When the
Armsession stops, the related timer tasks are cleaned up together.
3. Subscribe/Publish (SubPub)
TopicPubSubreceives WebSocket callbacks through the SDK. The WebSocket library's own threads are not described here.- The SDK maintains a
messagesqueue insideSubPubService;Receive()synchronously fetches the next message from the queue. - The queue holds up to
100messages and drops the oldest ones when full. - Multiple
Arm/TopicPubSubinstances may establish their own WebSocket connections concurrently; other live instances continue independently when one instance is destroyed or disconnected.
SDK Thread View
Thread Safety Rules
Arm Instance
- Single-thread access: each
Arminstance should be accessed by one business thread. - Multi-instance concurrency: create multiple
Arminstances if you need to control multiple robots concurrently. - Not thread-safe: do not share an
Arminstance across threads without external synchronization.
Service Modules
All service modules ( motionControl , controllerInfo , alarmClient , etc.) inherit the thread safety characteristics of their parent Arm instance:
cpp
// Safe: access all modules from one business thread
Arm arm; // Create one Arm session object
arm.Connect("192.168.110.2", ""); // Connect the robot on the current business thread
arm.motionControl.GetCurrentPose(PoseType::JOINT); // Read the current joint pose serially
arm.controllerInfo.GetCtrlStatus(); // Read controller status serially
arm.alarmClient.GetAllActiveAlarms(); // Read active alarms serially
// Unsafe: multiple threads access the same Arm
// Thread 1
arm.motionControl.MoveJoint(target1, 0.5, 0.5); // Thread 1 calls the motion API on this Arm// Thread 2 (concurrent)
arm.motionControl.MoveJoint(target2, 0.5, 0.5); // Undefined behaviorSafe Concurrent Access Pattern
cpp
// Safe: use multiple Arm instances for concurrent access
Arm arm1, arm2; // Two independent Arm sessions
arm1.Connect("192.168.110.2", ""); // Connect the first robot
arm2.Connect("192.168.110.3", ""); // Connect the second robot
// Thread 1
arm1.motion.MoveJoint(target1, 0.5, 0.5); // Send motion to the first robot
// Thread 2 (concurrent, different robot)
arm2.motion.MoveJoint(target2, 0.5, 0.5); // SafeCallback Guidelines
Do's
- Keep callbacks short and fast
- Copy data if later processing is needed
- Use thread-safe logging
- Return quickly so other notifications can proceed
Don'ts
- Block in callbacks
- Call blocking SDK APIs from callbacks
- Access other
Arminstances without synchronization - Throw exceptions from callbacks
Example: Good Callback Pattern
cpp
// Correct: fast, non-blocking callback
void OnJointPosition(const JointPositionMsg& msg) { // Enter the callback after a joint-position message arrives
std::lock_guard<std::mutex> lock(dataMutex_); // Protect the shared cache
latestJoints_ = msg.joints; // Copy the message quickly
} // Return immediately to avoid blocking the receive thread
void ProcessData() { // Process cached data on the business thread
std::vector<Float64> joints; // Use a local copy for business logic
{ // Keep the lock scope small
std::lock_guard<std::mutex> lock(dataMutex_); // Read the shared cache
joints = latestJoints_; // Copy the latest joints to the local variable
} // Release the lock so callbacks can continue updating the cache
// Process data safely outside the lock
} // End business-thread processingExample: Bad Callback Pattern
cpp
// Wrong: block inside the callback
void OnAlarm(const AlarmMsg& msg) { // Enter the callback after an alarm message arrives
arm.alarmClient.Reset(); // Do not call blocking SDK APIs in callbacks; this risks deadlock
ProcessLargeData(msg); // Do not run time-consuming work in callbacks; it blocks other callbacks
} // End the incorrect callback exampleRecommended Usage
- Manage
Connect()/Disconnect()/ mode switching for the sameArmfrom one business thread or one scheduling path. - In
TopicPubSubcallbacks, only do lightweight work such as parsing key fields, enqueuing data, or setting flags. - Run time-consuming logic, secondary control commands, and reconnection actions in business threads instead of blocking inside callbacks.
Constraints
Prohibited Reentrant Operations
Do not call the following from TopicPubSub callbacks:
Arm::Connect(),Arm::Disconnect()TopicPubSub::Connect(),TopicPubSub::Disconnect()StartReceiving(),RemoveMessageHandler()
In the current implementation, these reentrant operations may return OTHER_ERR or be ignored.
Other Constraints
- Do not assume different facades will hit the same robot in parallel; requests on the same
Armare safer to understand as serialized by default. - If your application requires high throughput or device isolation, use multiple
Arm/ArmHandle*sessions.
C99 Notes
- Call
Arm_Disconnect()beforeArm_Destroy()so the session's network thread and timer tasks are cleaned up normally.
Performance Considerations
Connection Recovery
If the network thread detects disconnection:
- The caller handles recovery by calling
Disconnect()and thenConnect() - All pending operations return error codes
Best Practices Summary
- Data copy: copy data in callbacks and process it in worker threads
- Error handling: always check return codes and handle disconnects gracefully
- Resource cleanup: always call
Disconnect()before destroying anArminstance