1.3 The Non-Blocking Mind
Stage I: The Substrate
"A robot cannot wait. The world does not pause while you compute."
1. Concurrency vs. Parallelism (The GIL)
These two words are often used interchangeably. They are enemies.
- Concurrency: Dealing with multiple things at once (Interleaving).
- Parallelism: Doing multiple things at the same time (Multi-core).
The Global Interpreter Lock (GIL)
CPython memory management (Reference Counting) is not thread-safe. To prevent race conditions on the reference counts, Python uses a mutex called the GIL. Rule: Only one thread can execute Python bytecode at a time.
- Implication: Adding threads to a CPU-bound task (like Image Processing) in Python will make it slower due to context switching overhead. You get no parallelism.
- Exception: I/O is distinct. When a thread waits for network/disk, it releases the GIL. This is why Threads are okay for Web Requests but bad for Computer Vision.
2. The Reactor Pattern (The Event Loop)
If we cannot use Threads for massive concurrency, what do we use? Asyncio. This is based on the Reactor Pattern.
- Select/Epoll: The OS facility to monitor 1000 file descriptors at once.
- The Loop: A
while Truecycle that asks the OS: "Who is ready?" - The Callback: When a file descriptor is ready, run its associated function.
This effectively turns a single-threaded program into a multitasking juggernaut, as long as you never block the loop.
3. Coroutines: The Theory of await
A function def foo(): is a subroutine. It runs, finishes, and returns.
A coroutine async def foo(): is a Generator on Steroids. It can:
- Yield Control: Pause execution and return control to the Loop.
- Maintain State: Keep its local variables alive while paused.
- Resume: Continue exactly where it left off.
The await Keyword
await future means:
"I cannot proceed without the result of this future. I yield the CPU to the Event Loop. Please wake me up when the data is ready."
This is the fundamental mechanic of ROS 2. A node awaits a service response, allowing other callbacks to process sensor data in the meantime.
4. Race Conditions & Locks
Even in single-threaded Asyncio, race conditions exist.
If Task A reads a variable, yields, and then Task B modifies it before Task A resumes, you have a bug.
The Critical Section
Any code block that modifies shared state must be atomic.
Since we yield explicitly in Asyncio (await), we have more control than threaded preemption, but we must still be vigilant.
The Golden Rule of Robotics Async:
"Never put a heavy computation (Matrix Multiplication) inside an async callback. It blocks the Heartbeat. The robot will die (Safety watchdog timeout)."
Deep FAQ
Q: When should I use Multiprocessing vs Threading? A:
- Threading: For I/O bound tasks (Network, Disk).
- Multiprocessing: For CPU bound tasks (CV, Path Planning). This spawns a new Python VM (Process) which has its own GIL.
Q: What is a Future? A: A Future is a promise. It is an object representing a result that hasn't happened yet. It creates a bridge between the "Now" (Synchronous) and the "Eventually" (Asynchronous).