Observation
Developers using Go employ significantly more concurrency and synchronization constructs than in Java.
Developers using Go for programming microservices expose significantly more runtime concurrency than other languages such as Java, Python, and NodeJS used for the same purpose.
Transparent capture-by-reference of free variables in goroutines is a recipe for data races.
Slices are highly confusing types that create subtle and hard to diagnose data races.
Built-in maps in Go make them commonly used. The array-style syntax of map accesses provides a false illusion of disjoint accesses of elements. However, map implementation is thread-unsafe in Go causing frequent data races.
Pass-by-value semantics are recommended in Go because it simplifies escape analysis and gives variables a better chance to be allocated on the stack, which reduces pressure on the garbage collector. Developers often err on the side of pass-by-value (or methods over values), which can cause non-trivial data races.
Mixed use of message passing (channels) and shared memory makes code complex and susceptible to data races.
Go offers more leeway in its group synchronization construct sync.WaitGroup. The number of participants is dynamic. Incorrect placement of Add and Done methods of a sync.WaitGroup lead to data races.
Running tests in parallel for Go’s table-driven test suite idiom can often cause data races, either in the product or test code.
Incorrect use of mutual exclusion primitives leads to data races.
Remark
Future programming language designers should carefully weigh different language features and coding idioms with their potential to create common or arcane concurrency bugs.
Dynamic Data Race Detection in Go Code
Explicit is better than implicit.