Contents

Programming Fundamentals › Concurrency & Async

Atomic Operation

An operation that completes entirely or not at all, with no interleaving.

Also known as: atomic, atomicity

An atomic operation is one that other threads can’t observe half-done. Either the whole change happens, or none of it does, and no other thread sees an in-between value. Atomicity is a property of the specific operation a language or library provides, not of a line of code that looks simple.

Here’s the difference in practice. A plain increment reads a value, adds one, and writes it back. Two threads can read the same value and both write back the same result, losing one update:

import threading, time

counter = 0
def unsafe_increment():
    global counter
    value = counter
    time.sleep(0)            # lets other threads run between read and write
    counter = value + 1

A lock makes the sequence one unit. A language may also offer atomic operations directly. JavaScript’s Atomics.add on a SharedArrayBuffer adds to a shared integer in one indivisible step and returns the previous value:

const shared = new Int32Array(new SharedArrayBuffer(4));
Atomics.add(shared, 0, 5);   // returns 0; the stored value is now 5

The trade-off is that atomic operations are fast and narrow. They cover single values and a few operations, so anything involving several fields still needs a lock or a different design. Using them correctly also takes care with memory ordering, which is subtle.

The classic mistake is assuming counter += 1 is atomic because it’s one line. In most languages it isn’t: it’s a read, an add and a write, and the same race can happen. Use a lock or an atomic type where the language provides one, and check the documentation for the specific operation. See mutex for the locking option.