It's good that someone with your experience is on this website. Appreciate your feedback on these comments. I have only briefly programmed threads, pipes, and processes for an upper-level GE course, but have never been able to implement it in my code. Even many of the things we learned about classes (in C++) seemed more like fancy kick-flips, and tricks, when it was easier to do an ollie. I'm just saying it seemed like a complicated way to do something when there was an easier way, that made more sense and was simpler (I'm talking about extends, operator overloading, try/catch, linked lists, and abstract structs).
I use threads almost entirely because they greatly improve the readability and maintainability of the source code over the long term AND because they provide methods that one actually NEEDS for robust, well-performing code. They are decidedly NOT complicated. They do not need to use complex structures, even. Nothing more than simple, fixed-sized arrays of integers is enough. One can use two arrays no more complicated than N[MAXTHREAD] and P[MAXTHREAD] as the 'previous' and 'next' pointers, for example. Fixed size arrays, no heap involved, no malloc() or free() needed. Seriously. This is dead easy stuff.
Let me relate a genuine story to make the point.
I was hired as a consultant for a company making instrumentation. They had exactly one programmer in their 20-person company, named "Bill." (I was hired in 1988.) Bill had managed to write all of the source code for an instrument they were selling, but his code was getting too complex for him to maintain and continue to improve as time went on. I was hired to write the PID controller software as an added feature to his code. But, once I was digging into his source code I realized that he'd done EVERYTHING without any threads, at all. It was all just 'big loops' of code, effectively just main() running around calling functions, plus some timer code.
A lot of his code did a bunch of math, table lookups, ADC reading, DAC writing, etc. And there was a lot of floating point code and library use. Since this was a 4MHz 8088 processor without an FP unit, it was quite slow. In order to monitor the user keypad, he had to poll it a lot. But his math wasn't done by the time he needed to re-poll the keypad, so he "salted" calls to the polling routine here and there and everywhere. Over time, as his other code grew, so also did he then find that the keypad was "non-responsive" at times, too. And so then he would ferret out where to salt in some more calls to the polling code to fix that problem. This was a never-ending process. He would change the main code doing the main work, adding more cpu processing, and ruining the keypad polling timing in the process. Then he'd fix that by packing in more calls to the polling code, etc. In an endless loop of creating and then hunting down problems just so the keypad would work right.
His keypad polling routine also had to deal with "shift" keys. So it had various states. Each time it was called to poll the keypad, it also had to remember where in all of its IF statements it was last at, the last time it was called, so that it could pick up where it left off. By the time I saw this code, it was ... very large, very ugly, and very hard to maintain, as well.
Interrupt handling was a possibility for this, but even so this would not fix the ugly code that had to handle the last known state of the scanning. So the interrupt code (which was already being used for other purposes) would soon acquire all of the terrible and ugly code that he'd developed for the keypad scanning.
Bill had NEVER programmed code to create threading before. So I sat down with him and spent one day explaining to him how to achieve it (and then went back the next day to working on my PID code.) That next day, in the afternoon, Bill came running into my office and told me that he'd "done it." It was working!! Bill, with zero experience to draw on and only my teaching him for a day, had sat down and written his own code to do cooperating threading and was fast on his way to fixing the problems in his code.
The cool thing about having a thread is that its state is retained when it switches away. It has its own stack. So, after polling the keypad and, perhaps, discovering a "shift" key had been hit, the code inside the IF statement that found this could simply "switch away" by calling switch() and give up its time. When the task switching code returned, it would return from the switch() call, returning exactly where it was with all the local variable state intact.
This shrunk the code down and made it very, very much more understandable and maintainable. It removed a lot of random boilerplate code that had been salted all over the place before. Etc.
The point here is that you want threads because they make your code EASY TO READ, EASY TO UNDERSTAND, EASY TO MAINTAIN, and they do NOT add any excess burdens. The only thing you need is a little RAM for the stack space and a tiny array to keep the last stack pointer for each thread. The switch() function itself is perhaps a dozen lines of assembly code. The whole thing takes almost no time at all to write and it "just works."
In my case, the O/S I'd mentioned earlier was 10 years later than this story above and had to be more complicated as it was destined to run a FAB process for making ICs. So it took me perhaps 20 hours of experienced effort. But it also just worked, supported thread messaging and per-thread exception handling, as well. Written in a mix of C and assembly in a small number of hours. Time that paid for itself over and over and over again as I continued writing the final application code to operate the FAB processes.
I think those people who LACK the familiarity and comfort in just "writing an O/S" on the fly, when needed, have a serious hole in their skill sets. At least, for embedded work. It's just too important a skill to have. Suppose someone facing the problem of sorting an array of numbers only knew how to attack the problem with a big subroutine with IF statements and no ability to use or understand LOOP constructs? Sure, to sort 100 numbers they could hard-code 100's of IF statements to achieve it. But it would be a horrible waste of effort and very hard to maintain, or extend if the need arose.
Sure, you can write a lot of useful code without being able to rapidly code up a thread-based system to simplify the problems ahead. But why go there? The code winds up being a horrible waste of extra effect, is very hard to maintain, and very difficult to extend when the needs arise. Being able to do so, means the rest of the code is maintainable, extensible, and easy to read and understand. It's night and day. Just as would be the case between that cumbersome sort routine by someone who didn't know how to use looping to help out vs someone who could. And writing a multithread O/S isn't hard to do. So it's just ALWAYS better to have the skill in your toolbox and pull it out when the problems reach the point where it makes sense (which is almost always, if the application is non-trivial.)
There are times where this is "more difficult." For example, the Microchip PIC16C54 has exactly four memory locations for storing the return address of a function call. And these stored return addresses cannot be read out by code. They also have 25 bytes of RAM, total. So this does complicate the idea of a multi-threaded system. It's not impossible. But it's almost never worth the benefits in a CPU this constrained for memory and lack of access to the return address stack. But for the 6502 or 65816 or 8080/8085 or even the 8051, it's quite doable. And easily done. (I just completed, a few years ago, a project written for the 8051 core (the SiLabs C8051F061 chip) where I implemented and used a multi-threaded O/S. That system took in 16-bit ADC data at a full 1 MHz rate, processed it, and generated accurate measurements with precision timing. And the code was far more maintainable because of the few days I spent implementing a small O/S on it.
What saddens me a bit is that anyone considers it hard to do. Because that means they will go to the ends of the Earth not to do it and will use all manner of ugly and hard to maintain code to "get by" without access to the sheer beauty that having multiple threads offers.
Again, to any and all, go get Douglas Comer's first XINU book: "Operating System Design: The Xinu Approach" dated 1984. NOT the 2nd edition, either. The 1st edition. There is a much later LinkSys version which I also enjoyed reading but he leaves out some very important ideas those decades later on. The first edition is the one to start with. It's very very easy to read and understand and provides exactly the kind of simple walk through someone would want when first learning to do a simple O/S. Nothing better!