liveBook · Manning

Modification order

As explained in C++ Concurrency in Action, Second Edition

A per-object order of all writes to that object, including its initialization, on which all threads in a given execution must agree.

Every C++ object has a modification order: the order of all writes to that object, beginning with its initialization. The order may differ between runs, but all threads in one execution must agree on it. This is a per-object rule, not a single timeline for the whole program: threads need not agree on the relative order of operations on different objects. section 5.1.3 section 5.3.3

For a non-atomic object shared between threads, you must provide sufficient synchronization; conflicting unsynchronized accesses can produce a data race and undefined behavior. Atomic operations keep accesses to their object within defined behavior, but they do not necessarily establish an order between different objects. section 5.1.3 section 5.1.2

Seeing a flag does not necessarily reveal an earlier write

Suppose one thread stores true to atomic x and then to atomic y, both with memory_order_relaxed. Another thread waits until it loads true from y, then loads x. That final load can still read false. Each object has a modification order, but relaxed operations do not make the writer's order across x and y visible as one order to the reader. If the store to y uses memory_order_release and the successful load of y uses memory_order_acquire, the earlier store to x becomes visible to the subsequent load, even when those x operations remain relaxed. section 5.3.3

Listing 5.5. Relaxed operations have few ordering requirements
#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed);     #1
    y.store(true,std::memory_order_relaxed);     #2
}
void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed));   #3
    if(x.load(std::memory_order_relaxed))        #4
        ++z;
}
int main()
{
    x=false;
    y=false;
    z=0;
    std::thread a(write_x_then_y);
    std::thread b(read_y_then_x);
    a.join();
    b.join();
    assert(z.load()!=0);                         #5
}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":7,\"ch\":49},{\"line\":7,\"ch\":51}],[{\"line\":8,\"ch\":49},{\"line\":8,\"ch\":51}],[{\"line\":12,\"ch\":49},{\"line\":12,\"ch\":51}],[{\"line\":13,\"ch\":49},{\"line\":13,\"ch\":51}],[{\"line\":25,\"ch\":49},{\"line\":25,\"ch\":51}]]"}
!@%STYLE%@!

The two-thread relaxed-atomic listing makes the surprising result concrete: its final assertion can fail even though the writer stores to x before y. section 5.3.3

Modification order still limits what one thread can observe of a single object. After a thread has seen a particular value, a later read cannot go backward to an earlier value in that object's order. A read following a write to the same object in the same thread must see that write or a later one. These limits do not, by themselves, create a total order across objects; sequential consistency supplies stronger ordering, while acquire-release can introduce selected ordering relationships. section 5.1.3 section 5.3.3

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Explains initialization, thread agreement, read constraints, and the limit of per-object ordering; leaves cross-object guarantees to memory-order rules.
Examples
Contrasts relaxed atomic flag reads that can miss an earlier write with acquire-release flag reads that order it.
What it assumes
Builds on objects, memory locations, and data races while introducing the atomic-operation rules needed for this distinction.
What you can do after
Diagnose/debug with it why atomic reads can disagree about operations on different objects without reversing either object's modification order.
How it connects
Connects modification order to data races, atomic operations, happens-before, relaxed ordering, acquire-release ordering, and sequential consistency.

Also explained in

Compare how 3 books explain modification order →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.