“Threads are hard, so you shouldn’t use them.” — the Internet
Unfortunately, I encounter this sentiment frequently. The subtext is: “threads are dangerous and can corrupt your state, so you shouldn’t use them at all.” While not technically incorrect, I find this belief damaging to hold, if only because it is intellectually stunting.
Threads are a powerful tool, and one that you will inevitably have to use, even if indirectly. They force you to think very deliberately about your data flow. They quickly expose whether your design is solid, because they require you to decouple the arrangement of work from the actual work at hand. Threads, due to their low-level nature, are not something you can drop in and forget about. The realities of your runtime environment (and the quality of it’s implementation) cannot be ignored.
I believe the majority of the benefit of threads comes from the act of using them. Due to the harsh realities of sharing mutable state between threads, most apps settle on a more sane strategy akin to passing messages between threads. This has a powerful effect on abstraction: worker threads are more easily conform to SRP because they simply don’t have access to the rest of the world. They’re isolated from the rest of the app for reasons of safety.
Currently, our tools are extremely primitive for working with threads. Blocking the runloop should be viewed as a amateur mistake: the UI thread is only for UI work, period. Real work happens in a background thread, with messages flying between threads to notify the user.