DEV Community

podboq
podboq

Posted on

atomic and mutex never ensure the execution order of threads

synopsis

whether one thread runs first or the other,is determined by the operating system's scheduling,which naturally results in a disorderly sequence.atomic and mutex can't guarantee the order.

Each core of a modern CPU has its own private L1/L2 cache and shares a L3 cache.When thread 1 modifies data,for performance reasons,it does not immediately write it back to main memory.Instead,it first writes it to the core's Store Buffer,and then asynchronously flushes it back to the cache/main memory.At this time,the core where thread 2 is located is completely unaware of this modification,and it still reads the old value from its own cache.Supplementary:The CPU has the MESI cache coherence protocol,but it can only guarantee "eventual cache coherence" and cannot guarantee "immediate visibility".The Store Buffer is a performance optimization designed to mask the delay of cache coherence.

When the compiler enables optimization(such as O2),it will directly cache frequently accessed variables in registers,without reading or writing memory throughout the process.For example,if a flag bit is repeatedly read in a loop,the compiler may only read memory the first time,and always read from the register thereafter,completely ignoring any changes made to the memory value by other threads.

std::atomic

It automatically prevents the compiler caching value in registers,ensuring every read and write access memory directly.

Automatically insert CPU memory barriers of the corresponding level to fore a flush of the Store Buffer and invalidate the cache,ensuring that modifications are visible to other threads.

At the same time,it ensures the atomic of single read-write operations,preventing the torn values during reads.

std::mutex

The semantics of mutex locks inherently include a complete memory barrier:the acquire semantics are executed when locking,and the release semantics are executed when unlocking.As long as both threads correctly lock/unlock before and after accessing shared data,visibility and atomic are naturally guaranteed.

Applicable scenarios:Scenarios where shared data is a complex structure and requires multiple steps of operation;the disadvantage is that the overhand is greater than that of atomic,with thread switching costs.

Memory Fence

This is a more fundamental mechanism,akin to manually inserting "synchronization points",which forces the CPU to flush the store buffer and invalidation read buffer,ensuring the read-write order and visibility before and after the barrier.

C++ standard:
std::atomic_thread_fence

Windows:
MemoryBarrier()

GCC:
_sync_synchronize()

Top comments (0)