Sep 5, 2026 · concurrency · robotics-basics · control-loops · robot-software
How Robots Handle Doing Many Things at Once
Robots do many things at once by splitting work into separate programs: threads suit small jobs, ROS 2 suits large graphs, HORUS suits one busy machine.
Robots do many things at once by splitting work across programs, and the choice is threads in one program, ROS 2, or a smaller middleware. Threads inside one program share everything and fail together, while ROS 2 and single-machine layers such as HORUS keep the parts separate so one slow job cannot stall a fast one. The verdict flips when the whole robot still fits in one loop. The rest of this post is for beginners who have watched a robot stutter, hesitate or drop readings, and want to know which part of the design caused that.
Your robot worked when it did one thing. It read the distance sensor and printed the number. Then you added the camera, and the number started arriving late. Then you added the part that decides where to drive, and the wheels began moving in small jerks, as though the robot were thinking between steps. Nothing crashed. Nothing printed an error. It simply got worse in a way you cannot point at. You added a print statement to find out where the time was going, and the print statement made it worse. You read that you should use threads, so you used threads, and now it works most of the time and locks up on Tuesdays for reasons nobody can reproduce. Somebody told you to use a real framework. Somebody else told you a framework is overkill for a robot this size. Both of them sounded certain. What you actually want to know is what is supposed to be happening inside a robot while it senses, thinks and drives at the same moment, and which of those arrangements you have accidentally built.
How does a robot do many things at once without falling over?
A robot does many things at once by giving each job its own program and moving data between those programs, rather than interleaving everything by hand inside one loop. The reason is not elegance. It is that the jobs a robot does run on wildly different clocks. The wheels want an answer on a fixed heartbeat, forever, without exception. The camera produces a picture whenever the camera is ready. The planner wakes up occasionally and thinks for a long time. A person sends a command once an hour. If all four share one loop, the slowest one sets the pace for everything, and the wheels inherit the camera's hesitation. Separating them means the wheel loop keeps its heartbeat whether the camera is late or not, and a late picture becomes an old picture rather than a stall. That separation is the whole trick. Everything below is about how far to take it, what to separate the parts with, and what the separation costs you in debugging, memory and patience.
What is actually happening inside a robot that runs several jobs at once?
Several programs are running side by side, each holding the most recent thing it was told, each producing something for the next one to read. Picture four boxes. The first talks to the distance sensor and announces a new reading whenever the sensor produces one. The second turns readings into a picture of where the walls are. The third decides where to go. The fourth turns that decision into current in the motors, on a strict heartbeat it never misses. None of the four waits for another to finish. Each reads whatever is most recent and gets on with its own job. When the third box is slow, the fourth keeps driving on the last decision, which is harmless for a moment and dangerous for longer. That is the real design question hiding behind the word concurrency: not how to make things run in parallel, but what each part should do when the data it wants is older than it expected. Robots that behave badly are usually robots with no answer to that question.
What are the actual options for running a robot's jobs side by side?
There are about six, and most projects seriously consider three. Threads inside one program come first: cheap to start, sharing all memory, and prone to the class of bug where two jobs touch the same variable at an unlucky moment. Separate programs talking over sockets or pipes come second, giving real isolation and asking you to define every message shape yourself. ROS 2 is third and by far the most common: a message layer, a build system and a large catalogue of drivers, planners and visual tools, adopted as a single decision. A single-machine middleware such as HORUS is fourth, an open-source Apache-2.0 layer where Rust, Python and C++ programs share the same shared-memory ring buffers, so a picture handed from one language to another on the same computer is not serialised on the way across, and none of the ROS package catalogue comes with it. A message broker such as MQTT is fifth, right for telemetry and wrong for anything the wheels are waiting on. A real-time operating system is sixth, and changes who gets the processor rather than how data moves.
Which of those arrangements fits which kind of robot?
The arrangement that fits is the one whose assumptions match the robot on your desk this month, not the robot in the funding deck or the tutorial.
| Option | Who it is for | What it assumes you know | When to pick it | When not to |
|---|---|---|---|---|
| One program, one loop | Beginners with a small robot | A loop and a language | Every job runs at the same pace | A slow job appears |
| Threads in one program | Builders adding a camera or a network wait | Locks, shared state, race conditions | The extra jobs mostly sit waiting | Motion depends on exact timing |
| Separate programs, plain sockets | People wanting isolation without a framework | Message shapes, ports, restarting things | The parts are few and stable | You would rebuild existing tools |
| ROS 2 | Robots needing drivers, mapping, navigation | Linux packaging, its build tool, its message model | The catalogue does real work for you | Nobody has evenings for setup |
| HORUS | One-machine robots mixing Rust, Python and C++ | One of those languages, life outside the catalogue | Sensing, control and glue share a board | You need ROS drivers or several machines |
| A broker such as MQTT | Robots reporting to a dashboard or a phone | Topics, retained messages, running a broker | Traffic is status and human commands | The robot waits on the answer |
| A real-time operating system | Teams whose motion has a hard deadline | Priorities, preemption, kernel settings | A missed cycle damages the machine | The real problem is how data moves |
| The platform vendor's SDK | People who bought a robot | Whatever model the vendor chose | The platform is the robot | You expect to change hardware |
What does it look like when a robot cannot keep up with everything at once?
A robot that cannot keep up hesitates rather than fails, which is exactly why the problem takes so long to diagnose. The arm reaches the shelf and pauses before the last part of the motion. The wheels track the wall smoothly for a while and then twitch. The robot passes the same test nine times and behaves differently the tenth, always when someone important is watching. Sensor readings arrive with gaps you can only see if you write down the arrival times. Logging gets worse the moment you add logging, because the log is now competing with the motion. None of this looks like a concurrency problem from the inside; it looks like a bug in the control code, and teams spend weeks there. The distinguishing sign is that the trouble scales with what else is running. Turn off the camera and the twitch stops. Turn off the planner and the arm finishes its reach. If disabling an unrelated part fixes the moving part, the fault is in how the parts share the machine, not in the maths.
Are you a hobbyist, a student, or someone shipping a product?
The answer changes with who you are, because the cost of getting this wrong is not the same for everyone. A hobbyist should start with one program and one loop and add a second program only when something forces it, because the fastest way to abandon a robot project is to spend the first month on architecture. A student on a course should follow whatever the course uses, since the marks and the help both come from there, and an unusual choice means debugging alone. A researcher wanting results should copy the arrangement used by the papers being replicated. Someone shipping a product should assume separate programs from the beginning, because the first field failure will be one part dying while the motors are still powered, and that behaviour has to be designed rather than discovered. Two engineers sharing a robot sit in the middle: separate enough that a crash is contained, small enough that a heavy framework eats the time they meant to spend on the machine.
Does a Raspberry Pi or a Jetson change how much you can run at once?
Yes, and more than people expect, because a small board runs out of memory and thermal headroom before running out of cores. On a small single-board computer, every program you add carries its own copy of libraries, its own start-up cost and its own share of the memory. Split a robot into a dozen programs on a board like that and the board spends its day copying data between them instead of driving the robot. The same split on a desktop machine costs nothing you would notice. This is where sharing memory between programs stops being a detail and starts being the reason the robot fits at all, because a camera frame that gets copied several times on the way to the planner is a camera frame the board pays for several times. If you are choosing hardware and software together, decide how many programs the robot needs first, then check the board can hold them, rather than discovering the ceiling three months in.
How much time do you have before the robot has to work?
If the deadline is weeks away, keep everything in one program and one language, and split only the one job that provably blocks the others. Splitting early feels responsible and costs you the demo. Every separation adds a decision about what happens when the other side is quiet, a way to start and stop things together, and a new place for a bug to hide between two programs where no debugger is looking. If the deadline is months away, spend an afternoon in week one drawing the boxes and the arrows between them, then build them one at a time, because retrofitting separation into a large single program means unpicking every shared variable by hand. If there is no deadline because this is a learning project, deliberately build the wrong version first: write the single loop, add the camera, watch the motion degrade, and then fix it. That hour teaches more than any explanation, this one included.
How much programming experience does this actually need?
Less than the vocabulary suggests, and the hard part is not the code. Reading from a message stream and writing to one is a handful of lines in any of the common languages, and beginners get that working the same day. What takes experience is the judgement around it: deciding that an old sensor reading should be ignored rather than used, deciding that the control loop should coast rather than stop when the planner goes quiet, deciding which failures should halt the machine and which should be logged and survived. Those are not programming skills; they are robot design skills, and they are learned by watching a robot do something wrong and working out which rule was missing. Threads are the exception, because they need real understanding of shared state to use safely, and the bugs are hard to reproduce. If you are early in your programming life, separate programs are the easier path even though they look heavier on paper.
What do you give up by splitting a robot into separate programs?
You give up the ability to see the whole robot in one debugger, and that loss is bigger than it sounds. In one program, you can stop the world and read every variable at the moment of the mistake. Across several programs, the mistake is in the gap between them: the message that arrived late, or twice, or never, while each program insists it did its own part correctly. You need new tools to see that gap, and building them yourself is where months disappear. You also give up simplicity of start-up, since six programs need starting together, stopping together and restarting when one dies. You give up some memory and some copying, unless the parts share memory rather than sending copies. In exchange you get containment, the freedom to write the control loop in one language and the glue in another, and the ability to change one part without rebuilding everything. Whether that trade is worth it depends almost entirely on whether the robot moves something heavy enough to matter.
When is ROS 2 the better choice?
ROS 2 is the better choice whenever the catalogue does work you were otherwise going to do yourself. If your robot needs mapping, localisation, a path planner or a driver for a common sensor, those exist in ROS 2, they have been debugged by thousands of people, and reimplementing them is a multi-year detour disguised as a weekend. ROS 2 also wins when the robot spans several computers, because the discovery and networking are handled for you rather than being your problem to invent. ROS 2 wins in a lab or a course where the shared vocabulary matters, and it wins on a job application, since it is the stack most employers name. HORUS is not the answer in any of those cases, and choosing it there means rebuilding a catalogue you could have had for free. The honest boundary is this: ROS 2 costs you setup time and returns ecosystem, so if you are not spending the ecosystem, you are only paying the cost.
Is threading always the wrong way to do two things at once?
No, and here is why. Threads are exactly right for jobs that spend their lives waiting rather than computing: a program that reads from a serial port, a listener holding a network connection open, a background task writing a log file to disk. Those threads sleep most of the time, wake up briefly and go back to sleep, and they interfere with nothing. The reputation threads have in robotics comes from a different use: two threads doing heavy work while sharing data structures, which is where the hard-to-reproduce bugs come from, and where a language without memory safety turns a race condition into a crash mid-motion. The rule that survives contact with real robots is that threads are fine for waiting and risky for computing, and that anything holding a deadline should not share a program with anything unbounded. Beginners are told threads are dangerous, then discover most real programs use threads, then conclude the advice was wrong. The advice was just missing the distinction.
Will a faster computer or more cores fix a robot that stutters?
Partly, but not the way you think. A faster machine does help, right up until the moment the problem stops being about total work and starts being about who waits for whom. If your motion loop stalls because it is waiting for a camera frame that has not arrived, a machine twice as quick gives you the same stall on a shorter schedule, and the robot still hesitates at the shelf. If two jobs are fighting over the same data with a lock between them, extra cores make the fighting more frequent, not less. Hardware helps when the machine is genuinely saturated, and hides the design problem for exactly as long as it takes you to add the next feature, which is why robots slow down as features arrive rather than at any single moment. The useful test is cheap: turn off one non-essential part. If the stutter vanishes, no processor upgrade is going to fix what is actually a queueing problem.
How do you decide which arrangement your robot needs?
Answer three questions in writing and the arrangement chooses itself. First, does anything in this robot have a deadline that hurts when missed, meaning something moves, something heavy, or something near a person? Second, how many languages will the finished robot contain? Third, how many pieces were you never going to write yourself? A deadline plus one language means separate programs with something simple between them. A deadline plus several languages on one computer means a layer where those languages share memory instead of copying data between themselves. A long list of pieces you will not write means ROS 2, whatever the other answers say, because the catalogue is the only item here that takes years to replace. No deadline and a short list means one program, one loop, and no middleware until the robot complains. Then write down what each part should do when its input goes quiet, before writing the parts, because that rule is the difference between a robot that hesitates and a robot that hurts something.
A short version, by situation:
- If you are a beginner with a small wheeled robot -> one program and one loop, because architecture spent before the robot moves is architecture spent on the wrong robot.
- If your motion stutters when the camera runs -> separate programs, because the camera and the wheels should never take turns.
- If your robot needs mapping or navigation you will not write -> ROS 2, because that catalogue is worth more than any property of a smaller stack.
- If the control loop is compiled and the glue is Python on one board -> a layer where both read the same buffers, because copying frames between languages is the cost you will keep paying.
- If several computers are involved -> a networked message layer, because shared memory stops at the edge of the machine.
When you want to compare options rather than symptoms, the HORUS Fit Framework lines them up on five things that are not numbers: ecosystem size, setup effort, team size fit, deployment target, and licence. For a first robot, setup effort and deployment target decide most of it, and ecosystem size decides the rest.
The repository is open source under Apache-2.0 and linked below. Star it so HORUS is in your list when you start building.