## **Lab 4** | Debugging Multithreaded Code from Lab 3 ### Design of Autonomous Systems ### CSCI 6907/4907 - Section 86 ### Prof. **Sibin Mohan** --- ## lab overview in this lab session, we will, - debug multithreaded code - multithreading safe vs unsafe functions - killing a multithreaded program - using delays (e.g., sleep) and loops, practically - learn more about synchronization - sharing big data structures (not just simple atomics) - printing and flushing print buffers --- ## multithreading safe vs unsafe functions - not all function can be called simultaneously from multiple threads - _e.g.,_ the `init()` → initialization functions are typically, - supposed to run once - from any thread - and update a global state (_e.g.,_ a file descriptor) --- ## safe vs unsafe functions [contd.] - functions using this global state need, - to wait for the initialization to finish - in lab 3 code, the init function was, - being called multiple times - from 2 different threads - thus init runs multiple times causing many problems --- ## safe vs unsafe functions [contd.] - many linux apis are marked `mt-safe` or unsafe for this reason lets jump into to → how to fix it? --- ## killing a multithreaded program - killing a multithreaded program isn’t simple - especially when we need to do cleanup before quitting - pressing `ctrl+c` might not kill all the threads so, - all threads must check an exit signal to stop - this means no unbounded loops - _e.g.,_ no `while (true) { ... }` --- ## killing a multithreaded program [contd.] - a signal can simply be a global `std::atomic
` running - so every loop in every thread is bounded by this signal - e.g. `while (true) { running.load() ||
}` --- ## killing a multithreaded program [contd.] - furthermore, during exit - it’s best to do some cleanup, _e.g.,_ - setting all the gpios to `off` - freeing up some special files/registers in a certain way - only one thread should do this clean-up --- lets jump into the code and see how that’s done --- ## using delays and loops, **practically**! - apart from avoiding unbounded loops, it’s a good idea to, - use **timeouts** → even for bounded loops - _e.g.,_ waiting for echo from the ultrasonic - most sensors don’t run at `2.4 ghz` (cpu speeds) so, - find out (estimate) the frequency you need to read the sensor at - find out what frequency the sensor can work at - use the lower number from the two - this process, - prevents sensor overuse (overheating) - improves system speed - reduces reading errors --- ## using delays and loops, **practically**! [contd.] - reading the sensor too fast means glitchy readings - reading speed can be controlled by adding delays - _e.g._, adding sleep(`0.5`) forces a reading loop → less than `2hz` --- lets jump into the code and see how that’s done --- ## takeaways - multithreaded programs need careful design as, - "simple" functions aren’t simple in multithreaded environment - exiting **cleanly** matters when dealing with sensors and actuators - unbounded loops are a recipe for disaster - **timeouts** are critical for system stability --- ## more about synchronization --- ## sharing big data structures - sharing data across multiple thread → easy when using **atomics** - however, atomics are limited size (_e.g.,_ a `float`/`int`) - when we need to share bigger objects, - _e.g.,_ a struct containing rover’s position, heading and velocity - use **locks** (_e.g.,_ a mutex) --- ## sharing big data structures | locks - locks enable → big structures - access optimized → breaking up big structs into smaller ones - _e.g.,_ **separate locks** for different fields of a `struct` - access sequence → optimized to **prioritize** certain threads - _e.g.,_ main control thread access priority supersedes other threads --- lets jump into the code and see how that’s done --- ## **printing/flushing print buffers** - printing → fundamental was to interact/understand program state - multithreaded embedded systems → careful design - UART does not work as fast as ssh - thus, printing and then flushing to UART slows the process down --- ## **printing/flushing print buffers** [contd.] - multiple threads printing → jumbled output+hard to debug - use **different colored print** to understand things better - avoid printing without newlines - **avoid printing too much** and too many times - significantly slows down the system - always avoid printing in **hot paths** - `1 hz` total printing rate → ideal for many systems --- **end of lab-4** ---