Why C/C++ volatile Is Not Atomic in Multithreading

Resumen

Pixel shares a DEV Community article explaining why volatile does not make shared C/C++ variables safe for concurrent access. It demonstrates how counter++ can lose updates because it involves separate read, increment, and write steps, and clarifies volatile's role in preventing certain compiler optimizations rather than providing atomicity or memory barriers.

DEV Community

The Most Dangerous Keyword in C/C++: Why volatile Is Not Atomic

The Most Dangerous Keyword in C/C++: Why volatile Is Not Atomic One of the most persistent myths in systems programming is the belief that declaring a shared variable as volatile makes it safe for concurrent multi-threaded access. Consider the following multi-threaded counter: volatile int counter = 0;

void worker() {
for (int i = 0; i < 100000; i++) {
counter++; // DATA RACE & LOST UPDATES!
}
}
Running two threads executing worker() concurrently will almost never result in 200,000. Increments will be silently lost. To understand why, we must examine the difference between Compiler Optimizations and CPU Hardware Coherence. 1. What volatile Actually Does The volatile keyword was introduced into the C standard in 1989 for a single specific purpose: interacting with Memory-Mapped I/O (MMIO) hardware registers and asynchronous signal handlers. It communicates one instruction to the compiler: "Do not cache this variable inside a CPU register. Do not optimize away repeated reads or writes. Every read must emit a fresh load from memory." // Without volatile:
while (!flag) {} // Compiler may optimize to: if (!flag) while(1){}

// With volatile:
while (!flag) {} // Compiler emits a CMP instruction reading RAM every loop iteration
2. What volatile Fails to Provide Failure 1: Zero Atomicity The operation counter++ is not a single atomic hardware instruction. It decomposes into a 3-step sequence in assembly: mov eax, dword ptr [counter] ; 1. Read from memory into register
add eax, 1 ; 2. Increment register value
mov dword ptr [counter], eax ; 3. Write back to memory
If Thread 1 reads counter = 5, and is preempted before Step 3, Thread 2 can read 5, increment to 6, and write back. When Thread 1 resumes, it writes 6, completely obliterating Thread 2's increment. Failure 2: Zero Memory Barriers Modern superscalar CPUs (x86, ARM64) execute instructions out of order to maximize execution pipeline throughput. volatile imposes constraints on the compiler, but it emits zero hardware memory barrier instructions (like MFENCE or DMB). The CPU is free to reorder reads and writes around volatile variables across cache lines. 3. The Modern Solution: std::atomic For thread-safe operations in C and C++, never use volatile. Use the standard atomic types: #include

std::atomic counter{0};

void worker() {
for (int i = 0; i < 100000; i++) {
counter.fetch_add(1, std::memory_order_relaxed); // Hardware Atomic!
}
}
On x86, this compiles to the hardware instruction lock inc [counter], asserting the CPU bus lock and guaranteeing atomic execution. Read the full systems engineering breakdown at Binary Dose.
Why C/C++ volatile Is Not Atomic in Multithreading
Powered by Readybot.io

Lo último de Pixel

Fix GitHub Pages: Missing Artifact Error

Pixel's announcement explains why the GitHub Pages deploy action reports that no github-pages artifact was found. It covers skipped or failed build jobs, missing uploads, …

Rotate DB and Redis Passwords Without Downtime

Pixel shares a practical guide to rotating exposed database and Redis passwords without interrupting application connections. The approach updates Secrets, changes service credentials, and gradually …

AI Agent Earns Crypto from $0: Day 3 Update

Pixel shares Day 3 of a 21-day experiment testing whether an autonomous AI agent can earn real crypto from zero. The agent launched a pay-per-call …

AI Agent Receipts Prove What Actually Executed

Pixel’s announcement examines how public Zambo receipts verify what an AI agent actually did. A BTC price fetch succeeded with a recomputable output hash, while …