There is a famous paper about how bad threads are from the 1990s. I believe it is valid on its own terms but misdiagnoses the underlying issue. The underlying issue is this: Taking multiple locks at once is not a sane operation at scale. Any system built on this will fail.
There is no magic bullet that solves all concurrency problems, but a combination of techniques, used with discipline, take it from insanely impossible to merely challenging:
1. Assign data to a thread, with only that thread able to access it. Offer concurrency via message passing to that thread alone, and don't allow references to escape from the thread (pointers, whatever you want to call it). Language support for this is nice (Erlang and Pony do it one way, Haskell another, Rust yet a third) but not technically necessary.
2. Using locks is valid, and even quite helpful and useful, but never let yourself take more than one. If you need transactions, see #1. Don't write code that encourages people to take more than one. Don't forget about things that may take locks without you thinking about it (e.g., in Go, taking a mutex and then trying to send on a channel is taking two locks); this is one thing that can be a bit of a challenge.
3. For more complicated things, use libraries as much as possible that have solved the super complicated cases already. For instance, databases that offer transactional security. You can do this even if you don't otherwise "need" such databases. Or libraries that offer a "parallel map" within your language, or other canned solutions to parallelism/concurrency.
4. Use whatever support your language provides for "linting" your conditions. The Go race condition checker is theoretically insufficient but practically useful (passing it doesn't mean that you have a safe program but it's a good start). Your language's mileage may vary but take advantage of what you can.
Honestly, just these things taken together can take you quite far. Again, these aren't magical solutions. There is an irreducible sense in which threading is harder than not threading, and I don't expect that to ever change. There will always be programmers that are more expert in it than others. But it doesn't have to be an impossible challenge anymore. And to a large degree, the challenge becomes less "solving the multithread problem" and more just figuring out how to architect systems under the restrictions of the patterns that work and compose together.
Although all that said, at a browser scale concurrency is always going to be a problem. They gotta go fast, but that fights the safe patterns pretty hard because they do involve a certain amount of runtime slowdown (e.g., passing a message is always going to be slower than just not passing a message). Rust is the only practical solution at that scale. But just as You Are Not Google, You Are Not Writing A Browser. More normal programs can go a long way with just what I laid out above.