The notion that multi-threading is hard to get right is a common one, but it does not match my experience. I've been running 100+ threads in C++ routinely for 20 years now.
So long as you are sensible (using mutexes for shared data structures, using queues or whatever for inter-thread messaging, using semaphores for inter-thread signaling, etc) it just isn't hard.
The only thing that briefly got me in trouble was when I tried my hand at lock-free data structures. Once they are working, they are fine, but it takes a few surprises before you really understand the A-B-A problem.
Yes, I suppose you can make multi-threading sound easy if you list all of the really easy problems. Mutexes, queues, and semaphores (or condition variables, if you have a modern code base) are all the easiest parts of multithreading.
But the "so long as you are sensible" advice is terribly, terribly wrong. You have to rethink how parts of your code are isolated from one another, and you have to avoid entirely new classes of errors—such as deadlocks, which can appear when you combine two well-tested and correct multithreaded components. And the number of threads has nothing to do with it.
This is just as applicable to any of the other tools to master multithreaded programming. Actors, as an example, are great so long as you are sensible and don't push your problems into your protocol too heavily. Unless, of course, you like complicated protocols.
So long as you are sensible (using mutexes for shared data structures, using queues or whatever for inter-thread messaging, using semaphores for inter-thread signaling, etc) it just isn't hard.
The only thing that briefly got me in trouble was when I tried my hand at lock-free data structures. Once they are working, they are fine, but it takes a few surprises before you really understand the A-B-A problem.