As you have seen, many of the problems with concurrent programs stem from sharing memory resources between multiple threads. This shared memory is used to communicate state and can be very fragile, with great care needed to ensure that everything stays up and running. In Go, concurrency is approached with the mantra:
Do not communicate by sharing memory; instead, share memory by communicating.
When you use mutexes and locks around a common object, you are communicating by sharing memory. Multiple threads look to the same memory location to alert and to provide information for the other threads to use. Instead of doing this, Go provides tools to help share memory by communicating.