iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
A language memory model is the contract that determines which results a concurrent program may produce. To reason about what one thread or goroutine can see another do, follow the language’s sequencing and synchronization rules—not assumptions about caches, processors, or a particular run. The practical test is whether the language defines a synchronization edge between the operations that communicate.
What is a memory model in concurrent programming?
A memory model gives meaning to concurrent reads and writes: it specifies which executions an implementation must allow, and which it must rule out. It describes how language-level operations relate through program order, synchronization, atomicity, and visibility. Compilers and processors may reorder or optimize operations only in ways consistent with that contract.
This is not simply a description of how a particular processor’s cache works. The same source program may run on different hardware or be transformed by different compilers; the language contract is the basis for deciding whether the resulting behavior is permitted.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →What does happens-before mean?
Happens-before is an ordering relation used to reason about whether an earlier action is ordered before a later one under a language’s rules. It is not a claim that the actions occurred at particular wall-clock times. Nor does writing one statement before another in a different thread automatically order them.
#1 Best Overall
In the Go memory model, happens-before is the transitive closure of sequenced-before and synchronized-before relations. The C++ working draft likewise defines it through sequencing, synchronization, and transitivity. In both cases, a programmer needs a real synchronization relationship to connect actions across threads; separate threads’ local program order alone does not do that.
A practical way to reason about visibility
- Identify the shared data and the operation that writes it.
- Identify the operation by which another thread receives or observes the update.
- Check the applicable language rule to see whether those operations synchronize, or otherwise establish an ordering relation.
- Follow the ordering transitively to the read in question. If there is no specified path, do not rely on the reader seeing the write.
When do acquire and release matter?
Acquire and release are useful when one thread publishes data for another without using a lock. The publishing thread performs ordinary writes and then a release operation. The receiving thread performs an acquire operation that observes the release (or, where the language specifies it, an operation linked to that release). The language then orders the earlier writes before later reads in the receiving thread.
For example, a Rust thread can write a payload and then store true to an AtomicBool with Ordering::Release. A receiving thread can load that flag with Ordering::Acquire and read the payload only after the load observes the published state. The payload must still be accessed in a way that is valid under Rust’s safety and synchronization rules; the atomic flag does not make arbitrary unsynchronized access to ordinary memory safe.
This is a language-level ordering guarantee, not a promise that a particular instruction “flushes the cache.” Rust’s Ordering documentation says that Relaxed imposes no ordering constraints on other memory accesses, while Release and Acquire can order surrounding operations when the acquire observes the release.
Rank #3
What the ordering names do—and do not—promise
- Relaxed: the atomic operation itself remains atomic, but it does not by itself publish unrelated memory.
- Release and Acquire: form a publication relationship when the acquire observes the relevant release under the language’s rules.
- AcqRel: combines acquire and release behavior for an operation that both receives and publishes synchronization.
- SeqCst: adds a single total order for sequentially consistent atomic operations, subject to the language’s rules. It is not a substitute for checking how ordinary data is synchronized.
Rust documents these ordering names as corresponding to C++20 atomic orderings except that Rust does not provide consume ordering. Similar vocabulary does not make the complete language models interchangeable.
Are atomic variables enough to prevent data races?
Not necessarily. Atomicity and ordering answer different questions. An atomic operation prevents a torn or conflicting non-atomic access to that atomic object, but a relaxed atomic flag does not automatically order ordinary reads and writes to other objects. To publish those objects, use a documented synchronization relationship or protect the shared state with a suitable lock or other mechanism.
Data-race consequences are language-specific. Rust’s std::sync::atomic documentation describes conflicting unsynchronized accesses, with at least one non-atomic access, as a data race and undefined behavior. Go treats races as errors and recommends serializing access. The Java Language Specification defines happens-before rules and warns that being race-free, or having sequentially consistent behavior, does not make a multi-operation group atomic.
Avoiding races in practice
- Prefer a mutex, channel, or other higher-level synchronization mechanism when it clearly expresses ownership or communication.
- If using atomics, state which exact operation publishes data and which receiving operation observes it.
- Do not mix atomic and non-atomic access to shared state unless the language’s rules make the pattern valid.
- Review compound invariants separately: individually atomic reads and writes do not make a sequence of operations indivisible.
How do Go, Java, C++, and Rust differ?
The languages share useful concepts such as sequencing and synchronization, but their specifications define different rules and consequences. The table summarizes the documented mechanisms and scopes; it is not a translation guide between languages.
| Language | Synchronization and ordering | Data-race and scope notes |
|---|---|---|
| Go | The Go memory model describes channels, mutexes, and synchronization primitives including sync/atomic. It defines happens-before from sequenced-before and synchronized-before relations. |
It says programs modifying data accessed simultaneously by multiple goroutines must serialize that access. Race-free programs receive the DRF-SC guarantee: their outcomes can be explained by a sequentially consistent interleaving. The referenced document is dated June 6, 2022. |
| Java | Java Language Specification Chapter 17 defines thread and memory semantics, including happens-before relationships and synchronization actions such as volatile accesses. | The cited specification is JLS 26. Its warning about multi-operation groups means race freedom or sequential consistency alone does not make a group atomic. Apply the JLS version relevant to the runtime; language rules are distinct from JVM implementation details. |
| C++ | The live C++ working draft’s intro.races section covers conflicting evaluations, mutex and atomic synchronization, acquire/release, relaxed atomics, and happens-before. |
The cited text is a working draft, whose wording and clause numbering may change. For production decisions, consult the applicable published C++ standard edition and library documentation. |
| Rust | Rust provides synchronization types and explicit atomic orderings: Relaxed, Release, Acquire, AcqRel, and SeqCst. Its atomic rules currently follow C++20 without consume ordering. | Rust’s std::sync::atomic documentation describes an access-based model and treats conflicting unsynchronized accesses with at least one non-atomic access as data races and undefined behavior. The cited Ordering documentation identifies std 1.99.0. |
The different race guarantees matter: Go’s DRF-SC statement is not a general promise for Java, C++, or Rust. Likewise, a Java volatile access, a Go channel operation, a C++ atomic, and a Rust atomic may each participate in synchronization, but only according to their own language’s rules.
How should you apply a memory model to a real program?
- Choose the governing specification. Check the language version or edition and the standard-library API documentation that apply to the program.
- Map the shared state. List the readers and writers for each shared object, including accesses hidden behind helper functions.
- Choose a synchronization mechanism. Use the language’s established mutexes, channels, synchronization types, or atomics rather than relying on timing.
- Trace the communication edge. For lock-based code, verify that unlock/lock operations pair as the language specifies; for publication with atomics, verify the acquire observes the relevant release.
- Check compound invariants. If correctness requires several fields or operations to change together, protect the group with an appropriate mechanism instead of assuming each field’s atomicity is enough.
For Go specifically, the official memory model’s practical advice is to serialize simultaneous access to modified data using channels or synchronization primitives. Its concise warning, “Don’t be clever,” is a reminder to prefer an explicit, documented synchronization design over subtle assumptions.
Quick Recap
What a memory model does not guarantee
- It does not guarantee that a program is logically correct merely because it has no data races.
- It does not make a group of individually valid operations atomic unless a language mechanism provides that property.
- It does not let a programmer infer visibility from elapsed time, source-code appearance, or hardware intuition.
- It does not make one language’s keyword or atomic ordering a safe stand-in for another’s.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

