I was reading the answer to this question regarding the volatile keyword:
https://stackoverflow.com/a/2485177/997112
The person says:
The solution to preventing reordering is to use a memory barrier, which indicates both to the compiler and the CPU that no memory access may be reordered across this point. Placing such barriers around our volatile variable access ensures that even non-volatile accesses won't be reordered across the volatile one, allowing us to write thread-safe code.
However, memory barriers also ensure that all pending reads/writes are executed when the barrier is reached, so it effectively gives us everything we need by itself, making volatile unnecessary. We can just remove the volatile qualifier entirely.
How is this "memory barrier" implemented in C++?
EDIT:
Could someone give a simple code example please?
In computing, a memory barrier, also known as a membar, memory fence or fence instruction, is a type of barrier instruction that causes a central processing unit (CPU) or compiler to enforce an ordering constraint on memory operations issued before and after the barrier instruction.
Memory barrier is implemented by the hardware processor. CPUs with different architectures have different memory barrier instructions. Therefore, the programmer needs to explicitly call memory barrier in the code to solve the preceding problem.
A write memory barrier gives a guarantee that all the STORE operations specified before the barrier will appear to happen before all the STORE operations specified after the barrier with respect to the other components of the system.
• The C++ memory model describes an abstract relation. between threads and memory. • The model makes guarantees about properties concerning. interaction between instruction sequences and variables in. memory.
This is very hardware-dependent. From the fairly long documentation of memory barrier of the Linux kernel:
The Linux kernel has eight basic CPU memory barriers:
TYPE MANDATORY SMP CONDITIONAL
=============== ======================= ===========================
GENERAL mb() smp_mb()
WRITE wmb() smp_wmb()
READ rmb() smp_rmb()
DATA DEPENDENCY read_barrier_depends() smp_read_barrier_depends()
Let's take one of them in particular: smp_mb()
.
If you open asm/x86/um/asm/barrier.h
, you will find that when CONFIG_SMP
is defined,
#define smp_mb() mb()
And if you scroll up, you can see that depending on the platform, mb has different implementations:
// on x86-32
#define mb() alternative("lock; addl $0,0(%%esp)", "mfence", X86_FEATURE_XMM2)
// on other platforms
#define mb() asm volatile("mfence" : : : "memory")
More information on the differences between these 2 things have been discussed in this thread. I hope this helps.
Memory barriers are trivial to use in C++11:
std::atomic<int> i;
All access to i
will be protected by memory barriers.
Typically, there are "intrinsic functions" - these are special functions that the compiler has special knowledge as to how they operate (in particular that they are memory barriers). The names vary from compiler to compiler (and sometimes for different architectures of the same compiler).
For example, MSVC uses _ReadBarrier
, WriteBarrier
and _ReadWriteBarrier
In x86 it would produce an lfence
, sfence
or mfence
instruction - which, respectively, does "load", "store" and "all memory operations" barriers - in other words, an lfence
will be a barrier for memory read operations, an sfence
will be a "memory write" barrier, and mfence
will be a barrier against both read and write operations.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With