This closing section is deliberately practical: given everything else in this manual, how do you actually catch a bug, or find one that's already loose in production? Go's built-in race detector is the standout tool of the five -- nothing else here comes close to catching data races automatically. The rest is a mix of rich concurrent-collection libraries (Java), thread/coroutine dump tooling (jstack, py-spy), and the simple fact that a single-threaded runtime (JS, and Python's GIL for many cases) structurally prevents an entire category of bug from occurring at all.
Language Verdict: Pros, Cons & Recommendation
- -race is a genuine, mainstream, everyday-usable automated data-race detector -- the standout tool of this whole manual
- SIGQUIT goroutine dumps plus automatic all-blocked deadlock detection are built in with zero setup
- sync.Map's narrow, unusual fit means it's frequently reached for incorrectly instead of a mutex-guarded map
- No language-level immutability enforcement -- 'immutable' structs are convention only, unlike Java's record or Python's frozen dataclass
- Richest built-in concurrent-collections family (ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue)
- jstack's explicit 'Found one Java-level deadlock' detection is genuinely useful, mature tooling
- No automated data-race detector at all, unlike Go
- record/List.of() immutability helps, but there's no single 'always safe to share' guarantee as strong as a frozen dataclass or Object.freeze
- Full access to Java's concurrent collections, plus StateFlow as a coroutine-native immutable-snapshot tool
- kotlinx-lincheck gives genuinely rigorous testing for hand-rolled concurrent algorithms
- Plain jstack dumps are often not useful for diagnosing a stuck coroutine -- need the separate DebugProbes tool
- Same lack of an automated whole-program race detector as Java
- frozen=True dataclasses give real, enforced immutability with zero extra ceremony
- py-spy gives a genuinely easy, no-instrumentation-needed production stack dump
- No dedicated fully-safe-for-compound-access concurrent collection beyond queue.Queue
- No automated race detector at all -- and the GIL's partial safety net makes people less likely to look for one
- Object.freeze gives real enforced immutability with no library needed
- The entire 'concurrent collection' and 'data race' problem space is structurally absent for the common single-threaded case
- No good single-dump diagnostic for a stuck async chain -- 'stuck' looks like pending work, not a blocked thread
- Zero tooling at all for the rare but real SharedArrayBuffer-race case
Recommendation: If Go is on the table for a CPU/concurrency-heavy service, mention -race explicitly -- it's the single most differentiated piece of tooling in this whole manual. Otherwise, default to each language's native concurrent-collection type over a plain one guarded by hand, and know your production stack-dump tool (jstack, goroutine SIGQUIT dump, py-spy, Node's diagnostic report) before you need it under pressure.
Concurrency Mechanics, Side by Side
var mu sync.Mutex
m := make(map[string]int) // plain map -- NOT safe for concurrent use, guard with mu
var sm sync.Map // built-in concurrent map, but a narrower, different-shaped API
sm.Store("key", 1)
value, ok := sm.Load("key")
Go's built-in map type is explicitly NOT safe for concurrent read+write access (the runtime will detect this and crash with 'fatal error: concurrent map read and map write' in many cases, which is at least better than silent corruption) -- the idiomatic fix for most code is simply a mutex-guarded plain map. sync.Map exists for a narrower set of access patterns (many goroutines reading/writing disjoint keys, few iterations) and is explicitly documented as usually the wrong choice compared to a mutex-guarded map unless you specifically match that pattern.
type Config struct { Timeout int; Retries int } // no built-in immutability enforcement
var configPtr atomic.Pointer[Config]
configPtr.Store(&newConfig) // readers atomic.Pointer.Load() a whole new struct
Go has no language-level immutability enforcement (no final/val equivalent for struct fields) -- 'immutable by convention' means simply never mutating a struct after constructing it and relying on team discipline, not a compiler guarantee. atomic.Pointer[T] (generics, 1.19+) gives the same swap-a-whole-snapshot pattern as Java's AtomicReference.
Go's race detector is the standout tool in this entire manual -- nothing else here comes close to automatically catching a data race before it causes a production incident.
$ go test -race ./...
$ go run -race main.go
// Output on a real race:
// WARNING: DATA RACE
// Write at 0x00c0000a4010 by goroutine 7: ...
// Previous read at 0x00c0000a4010 by goroutine 6: ...
This is a genuine, oft-cited Go differentiator: -race instruments every memory access and goroutine synchronization event at compile time (using Google's ThreadSanitizer under the hood) and reports the exact two conflicting accesses, with both goroutines' stack traces, the moment a real race actually occurs during a test run. The trade-offs are real too -- 5-10x memory overhead and 2-20x slower execution -- so it's a testing/CI tool, not something you run in production, and it only catches races that actually execute during the run, not every theoretically possible one.
$ kill -QUIT <pid> # or SIGABRT -- prints a full goroutine dump to stderr
// Every goroutine's state and stack, automatically, including the
// deadlock-detection crash covered earlier if ALL goroutines are blocked.
import "net/http/pprof" // also exposes /debug/pprof/goroutine over HTTP
Sending SIGQUIT dumps every goroutine's stack automatically, no extra tooling required, and the runtime's own all-goroutines-blocked deadlock detection (covered in the locks section) means the worst case often crashes with full diagnostics rather than silently hanging forever. net/http/pprof is the additional, widely-used tool for live production inspection (goroutine counts, blocking profiles) without even needing to signal the process.