Concurrency: The Typewriter Jam Problem
You never realize you have a strange upbringing.
No matter what your childhood, you naturally assume that what you have is normal and everyone else has largely the same childhood experience, albeit it at a different address.
For that reason I thought it was completely normal for my dad to have a collection of antique mechanical typewriters. I even remember playing with the keyboards growing up.
I would depress a key, (far deeper than the keys on my MacBook), and as I did, a mechanical leaver would rise up to bring a print head to strike the surface and type a character. I loved slowly pushing the keys down to see the corresponding levers rise as I did.
Many of the design decisions of computers are influenced by the typewriter. Lost of people knew how to work a typewriter. It only made sense to use the same interface for the computer. But few today will look at a computer and say “What is this thing? Oh, it’s like a typewriter! I know how to use those, so this shouldn’t be too different.” Nevertheless, the design persists. In fact, the action of the typewriter was based on the piano, and the piano has its roots in the pipe organ, which is more than 2,000 years old.
The kind of mechanical typewriters my dad had were based on a design by Christopher Latham Sholes and Carlos S. Glidden in 1867, early versions of which used a piano keyboard. This was changed into the familiar QWERTY layout by 1873 when Gun Smiths E. Remington & Sons who, in a swords-to-plowshares move, started manufacturing typewriters, sewing machines and farm equipment.
One of the early problems Sholes and Glidden encountered was the tendency of the print heads to jam. Unlike a piano, where each hammer has its own string, all 40 typewriter print heads share a single striking surface. The levers were arranged alphabetically in a circle around the target. But when you type the word “stylist”, for example, the S and T levers were so close together that they would catch each other and jam the machine. Later versions put levers like S and T, commonly used together in english, on opposite sides of the circle. But that solution could not stop me. I’d just depress all the keys together and watch the ensuing train wreck.
This is the essential nature of a problem in computing known as concurrency. What to do when there are multiple independent computing threads reading and writing to a shared resource, such as a database? Much like the case of a typewriter, there are things that can go wrong that would never happen if you were using a single thread. Multi-threaded concurrent processes are a much more complex problem than a single-threaded solution.
So why do it at all? Concurrent processes can solve certain kinds of hairy computer problems, especially ones that require a lot of calculations done quickly. Since almost all computers have multi-core CPUs (they have 2 or more brains), it means that the cores can divide up the computational labor and return a result more quickly. Even if I was to give up on concurrency, accept longer wait times for processes to finish, and use no more than 1/2 the power of my hardware, I still couldn’t get away from it. The value of having a server with multiple clients, or millions of users on a single shared forum like Facebook or Twitter means I will have to brave the whips and scorns of concurrency’s complexity. The payoff is too great.
“Be Prepared” is the Boy Scout motto, and so 8th Light has me preparing for concurrency by reading chapter 13 in Clean Code by Robert C. Martin. The key things I’ve learned is that bugs (jams) can happen in concurrent processes in a way that would never be possible in a single-threaded application. Bugs can occur irregularly; one in two, a thousand, even a million times or more. It’s important to remember that when these happen to not write them off as one-off bit-flipping by cosmic rays or errant ghosts. In order to ferret out those bugs, I’ll need new tools to reveal them by having the threads take different paths to their destinations. For that matter, I’ll need a much deeper understanding of the internal workings of my compilers, interpreters, environments and operating systems.
Another thing I will need is a serious and disciplined commitment to clean code. Trying to deal with bugs caused by a problem in a single thread while also dealing with the bugs emerging from the complexity of a multi-threaded application is just asking for trouble. I’ll need to double-down on unit tests, using good design decisions, decoupling my modules and refactoring for clean abstractions. I’ll also need to keep my concurrent code completely separate from anything that can be verified by a single-threaded test.
This gets into another layer of the Imperative Shell-Functional Core pattern. When designing a car, don’t put the transmission in the tire treads. When designing a concurrent system, keep the single-threaded code out of the concurrent code. The concurrent code creates hair-raising complexity by its very nature, so keep that part of the system as simple and isolated as possible.
Keep knowledge sharing on a locked-down need-to-know basis. More than ever it’s important to regulate who gets to change what and when. By writing code that “does not know and has no opinion” about the rest of the application, I can limit the complexity and reduce the amount of potential problems emerging from concurrent systems. They will be tough enough to solve when there are only a hand full of them.
There are also new design patterns that can aid in managing the complexity inherent in concurrent systems. There is the Actor-Model pattern, along with conceptual helpers such as Producer-Consumer, Reader-Writer, and Dining Philosophers, all of which will help to identify problems in the system’s design.
As far as practical advice, its a good idea to start with getting the single threaded code up first and making sure the test coverage and design are strong and beautiful. Then start on the concurrent elements, beginning with a proper shut-down procedure, which is one of the trickiest things to choreograph. That could become a real time sink if I do it after the system does a lot more than turn itself off. Better to get that working first and keep it working as I extend out the desired behavior.
The problems with typewriter jams pale in comparison to the kind of nightmare tangles that concurrent code can produce, but being forewarned is forearmed. This will help me to understand the reason 8th Light is so serious about writing clean code. It’s not just an aesthetic choice or some marketing gimmick. It’s a foundation for more powerful and advanced computing that can deliver needed value to our world’s community.