DriversRecommendedOutdated drivers can make a good PC feel brokenScan driver issues before chasing fixes manually.Scan NowOctober DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run Scan×
Skip to content
World desk4 min

Why `count++` Breaks Under Concurrency: Atomicity, Visibility, and Ordering

A plain shared count++ is a read-modify-write that can lose updates. Learn what atomic increments fix—and what they do not guarantee about other shared data.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

count++ can lose updates when multiple threads or goroutines change the same ordinary shared variable. The increment is usually a read, a calculation, and a write—not one indivisible action. An atomic increment makes that counter update indivisible, but it does not automatically synchronize every other value in your program. The exact consequences of an unsynchronized access depend on the language’s memory model.

Why does count++ fail under concurrency?

Suppose count starts at 0 and two workers each increment it. A typical interleaving is:

As an Amazon Associate I earn from qualifying purchases.

  1. Worker A reads 0.
  2. Worker B reads 0.
  3. Worker A adds 1 and stores 1.
  4. Worker B adds 1 and stores 1.

The final value is 1, although two increments ran. This is a lost update: each worker computed from the same old value, and the later store overwrote the earlier one. It illustrates one possible failure, not the only behavior a language may permit for a data race.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Is count++ atomic?

Not for an ordinary shared variable. It is a compound read-modify-write operation: load the old value, calculate a new one, then store it. Another worker can intervene between those steps. In C++, for example, an integral std::atomic supports increment and fetch_add; its atomic read-modify-write is specified as one operation, rather than a separate load and store. cppreference: std::atomic

What do atomicity, visibility, and ordering mean?

Atomicity: one update cannot be torn into competing steps

An atomic increment prevents another operation on that atomic object from slipping between the increment’s read and write in a way that loses the update. It solves the single-counter update problem when all relevant accesses use the appropriate atomic operations.

Visibility: another thread can observe published writes

Visibility concerns whether one thread’s writes become observable to another under the language’s synchronization rules. Making one counter increment atomic does not, by itself, publish arbitrary neighboring state.

Ordering: synchronization constrains which operations may be observed first

Memory ordering specifies relationships among operations across threads. It matters when an atomic variable participates in a protocol involving other data. The guarantee comes from the selected language operation and ordering, not merely from the fact that a variable is atomic.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Does volatile make increment thread-safe?

Do not treat volatile as a substitute for an atomic read-modify-write or a lock. The meaning of volatile is language-specific, and it does not generally turn a sequence of reading, adding, and writing into one indivisible increment. Use the concurrency primitive defined by the language for the required guarantee; consult that language’s memory model before relying on a volatile access for synchronization.

Choose a mechanism for the invariant you need

Mechanism One counter update indivisible? Coordinates related state? Best fit
Ordinary shared increment No No synchronization guarantee Not safe for unsynchronized concurrent updates
Atomic increment Yes, for the atomic operation Only when the operation’s ordering and protocol establish the needed relationship A counter whose update is the whole invariant, or a carefully designed atomic protocol
Lock or mutex around the update Yes, for code consistently protected by the same lock Can protect multiple related values as one critical section Compound invariants that span counter and other state

If correctness depends on updating a counter together with another field—for example, a count and the collection it describes—protect the whole invariant with a lock or another design that serializes those changes. Making only the count atomic can leave the combined state inconsistent.

Language examples and memory-model differences

C++: atomic counter when the counter alone needs atomicity

A minimal C++ counter can be declared and incremented as follows:

#include <atomic>

std::atomic<int> count{0};
void record() {
    count.fetch_add(1, std::memory_order_relaxed);
}

memory_order_relaxed keeps the counter operation atomic, but adds no ordering or synchronization for unrelated data. That can be suitable when the counter value itself is all the program needs from this operation. If the increment is meant to publish or coordinate other state, choose a protocol and memory order that actually establishes the required relationship; do not infer it from atomicity alone. cppreference: std::atomic

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Rust: select ordering to match the protocol

Rust’s atomic operations expose explicit orderings. Relaxed provides atomicity for that operation without ordering other operations. Release and Acquire can establish synchronization when an acquire operation observes the relevant release operation. AcqRel combines those roles for a read-modify-write operation. SeqCst additionally places sequentially consistent operations in one total order. These are protocol guarantees, not a ranking where the strongest option is automatically the right design.

Rust documents data races involving conflicting unsynchronized access where one access is non-atomic as undefined behavior. Its atomic and ordering documentation describes the available guarantees and their conditions. Rust stable atomic module · Rust stable ordering documentation

Go: serialize shared mutable access

The Go Memory Model’s advice is direct: “Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.” It names channel operations and synchronization primitives such as sync and sync/atomic as ways to do that. The model also describes happens-before and its DRF-SC guarantee: data-race-free programs behave as if goroutines were interleaved on a single processor. Go Memory Model (identified as version of June 6, 2022)

Java: use Java’s own memory model

Java defines its own thread and memory model in the Java Language Specification. Do not import the exact meaning of a C++ memory order or Go’s race rules into Java; use Java’s rules and APIs for the synchronization you need. Java SE 26 JLS, Chapter 17

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

When is an atomic counter not enough?

  • Several values must change together: use a lock or another protocol that protects the complete invariant.
  • The counter signals that other data is ready: choose synchronization with explicit publication and ordering guarantees, not merely an atomic increment.
  • Some accesses are atomic and others are ordinary: mixing access styles may still violate the language’s rules. Make all concurrent access follow one coherent synchronization design.
  • You are unsure what the language permits: consult its official memory-model documentation; data-race consequences differ by language.

Further reading for Java developers

Java Concurrency in Practice is a Java-specific supplemental book published in 2006. Pearson lists atomic variables, nonblocking algorithms, and the Java Memory Model among its topics. Use it for foundational context, and rely on current Java documentation for current API details. Pearson publisher listing

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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.