Memory Model and Synchronization

Purpose and Scope

This page explains how the Go source tree represents the synchronization operations that programmers use to make concurrent programs predictable. The term “synchronizes before” is the key memory-ordering relationship used by Go documentation: when one operation synchronizes before another, effects made visible by the first operation can be relied on by the second according to the Go memory model. The repository evidence here focuses on four surfaces: low-level atomics, mutexes, channels, and the race detector runtime that helps diagnose unsynchronized shared memory access.

Sources: src/sync/atomic/doc.go, src/sync/mutex.go, src/runtime/chan.go, src/runtime/race/README

Go’s first-party guidance deliberately does not present atomics as the default way to coordinate goroutines. The atomic package documentation says its functions are low-level primitives for implementing synchronization algorithms, require great care, and are usually less appropriate than channels or the synchronization facilities in package sync. That framing matters when reading the code: atomics define strong ordering guarantees, but the public API nudges most programs toward clearer ownership transfer through communication or explicit locking with higher-level types.

Sources: src/sync/atomic/doc.go

Relevant Source Files

  • src/sync/atomic/doc.go — Defines package-level documentation for low-level atomic memory primitives, their sequentially consistent ordering model, supported operations, and alignment cautions.
  • src/sync/mutex.go — Defines sync.Mutex, sync.Locker, Lock, TryLock, and Unlock, including the memory-model relationship between Unlock and later Lock calls.
  • src/runtime/chan.go — Implements Go channels in the runtime, including the hchan data structure, send and receive wait queues, buffered-channel invariants, allocation behavior, and the internal lock protecting channel state.
  • src/runtime/race/README — Documents the race detector runtime library bundled in the tree, its ThreadSanitizer origin, and the process for updating platform-specific runtime object files.

Core Synchronization Primitives

The smallest explicit memory-ordering API in this evidence is package sync/atomic. Its documentation defines swap, compare-and-swap, add, load, and store operations in terms of simple source-level effects, then gives them a global ordering rule: if an atomic operation A is observed by atomic operation B, A synchronizes before B, and all atomic operations in a program behave as though executed in a single sequentially consistent order. In practical terms, Go does not expose relaxed, acquire-only, or release-only variants in this API surface; the documented model matches C++ sequentially consistent atomics and Java volatile variables.

Sources: src/sync/atomic/doc.go

Package sync builds a more ergonomic public contract for mutual exclusion. A Mutex has an unlocked zero value, must not be copied after first use, and exposes Lock, TryLock, and Unlock through a small wrapper over internal/sync.Mutex. The memory-model statement is precise: the nth Unlock synchronizes before the mth Lock for any n less than m. A successful TryLock is equivalent to Lock for this purpose, while a failed TryLock establishes no synchronizes-before relationship. That last distinction prevents readers from treating a failed polling attempt as a visibility guarantee.

Sources: src/sync/mutex.go

Channels are implemented in the runtime rather than in package sync, but they are part of the everyday synchronization model for Go programs. The channel implementation centers on hchan, which records buffered element count, buffer size, circular-buffer pointer, element type and size, send and receive indices, send and receive wait queues, closed state, optional timer association, and an internal mutex. The file-level invariants state that at least one of the send and receive queues is empty, with a special select-related exception for an unbuffered channel, and they add stronger relationships for buffered channels based on whether the circular queue is empty or full.

Sources: src/runtime/chan.go

System-to-Code Mapping

ConceptPublic meaningRepository implementation signal
Atomic operationLow-level synchronization primitive with sequentially consistent behaviorPackage documentation and primitive declarations in src/sync/atomic/doc.go
MutexMutual exclusion lock whose Unlock makes prior work visible to later Lock callsPublic wrapper and memory-model comment in src/sync/mutex.go
ChannelCommunication mechanism backed by queues, buffer state, and a runtime lockhchan, waitq, makechan, and invariants in src/runtime/chan.go
Race detector runtimeInstrumentation support for detecting conflicting concurrent accessesThreadSanitizer-based runtime objects described in src/runtime/race/README

This mapping is useful because the programmer-facing model and implementation model are intentionally at different levels. The atomic and mutex files are public package sources with documentation comments that become API documentation. The channel file is runtime code: it explains how a language feature is represented internally, not a package API users import. The race README is operational documentation for a bundled runtime component, showing that race detection is shipped as platform-specific object files derived from ThreadSanitizer rather than as ordinary Go source alone.

Sources: src/sync/atomic/doc.go, src/sync/mutex.go, src/runtime/chan.go, src/runtime/race/README

Execution Flow and Memory Ordering

A mutex-protected critical section can be read as a simple ordering chain. One goroutine calls Lock, reads or writes shared state, and calls Unlock. A later goroutine that successfully calls Lock on the same Mutex observes an operation that is ordered after that earlier Unlock. The source does not say that a Mutex belongs to the goroutine that locked it; in fact, Unlock documents that a locked Mutex is not associated with a particular goroutine and may be unlocked by another goroutine. That property supports handoff patterns, but the same file warns that values containing sync types should not be copied.

Sources: src/sync/mutex.go

A channel operation follows a different implementation path. makechan validates element size, alignment, and buffer size, then allocates an hchan and, depending on whether elements contain pointers, may allocate the channel header and data buffer together or separately. The hchan lock protects channel fields and several fields in blocked sudogs, while comments warn not to ready another goroutine while holding that lock because doing so can deadlock with stack shrinking. That note shows how synchronization is not only a language-level concern: it must also preserve scheduler and garbage collector safety inside the runtime.

Sources: src/runtime/chan.go

Atomic operations are best understood as building blocks for specialized data structures rather than as a replacement for every lock. The documentation includes alignment cautions for some 64-bit operations on 32-bit architectures and points users toward typed atomic values such as Int64 and Uint64 for automatic alignment. It also notes that only a few integer sizes are supported because non-word-sized atomic operations may be inefficient or infeasible. These details are part of the API contract: correct synchronization depends on both ordering semantics and valid machine-level access.

Sources: src/sync/atomic/doc.go

Race Detection and Debugging Signals

The official race detector documentation defines a data race as concurrent access to the same variable where at least one access is a write, and it recommends enabling the detector with the race flag on common go command workflows such as testing, running, building, and installing. The repository evidence for this page is the runtime side: src/runtime/race/README states that the runtime/race package contains the data race detector runtime library and that it is based on ThreadSanitizer from LLVM. That tells contributors where the bundled detector runtime comes from and why platform-specific object files appear under the package.

Sources: src/runtime/race/README

Race detection complements the memory model rather than replacing it. A program can use Mutex, channels, or atomic operations to establish ordering, and the detector is designed to report cases where shared memory is accessed without adequate synchronization during an instrumented run. Because the README lists per-platform syso artifacts and the racebuild update tool, maintainers should treat race runtime updates as toolchain integration work, not as normal package editing. Application developers, meanwhile, should use race-enabled test runs as a diagnostic signal when changing concurrent code.

Sources: src/runtime/race/README

Practical Guidance and Next Steps

When writing concurrent Go code, start with ownership transfer and communication where it fits, use Mutex when shared state needs a clear critical section, and reserve sync/atomic for narrow low-level algorithms whose invariants can be documented and tested. If you use TryLock, remember that failure does not provide visibility or ordering; it is only a failed attempt. If you use atomics, prefer the typed wrappers mentioned by the atomic documentation when they make alignment and address handling less error-prone. If you use channels, remember that their simple syntax is backed by runtime queues, locks, buffering rules, and scheduler interactions.

Sources: src/sync/atomic/doc.go, src/sync/mutex.go, src/runtime/chan.go

For deeper reading, pair this page with the dedicated race detector and diagnostics pages when debugging a suspected concurrent access bug, and with the language or runtime pages when you need to understand how goroutines, channels, and the scheduler interact. For API-level work, read the package documentation generated from sync and sync/atomic before using lower-level primitives. For repository maintenance, changes to channel internals, atomic contracts, mutex semantics, or race runtime artifacts should be reviewed as changes to the observable concurrency story of the Go distribution, not merely as isolated implementation edits.

Sources: src/sync/atomic/doc.go, src/sync/mutex.go, src/runtime/chan.go, src/runtime/race/README