No, downvote arrow is only after you read the comment, not before. Can you show where I wrote "multi-threaded" in my comment?
Shared memory and processes is a different beast than just threads in same process. Memory was mmap-ed between the two OS procs. Didn't use locks in that part of code for a list of reasons..., and memory write order didn't matter as much (so didn't see an immediate need for memory fences, but someone below suggested those might work to fix compiler optimizing out accesses when nor in debug mode).
Not sure what hinted at frequent use of volatile, it was the only use in 10 years. That is what made it memorable.
Did some more research. Compilers have their own intrinsics that only affect the code generated (in addition to the processor ones). VS has _ReadBarrier, _WriteBarrier and _ReadWriteBarrier while GCC has asm volatile("" ::: "memory"); to prevent reordering optimizations.
There really isn't any difference between multithread with common processes memory and multiprocess with mmaped shared memory. Both have mutliple threads accessing the same physical memory but in one case the OS made the memory common and the other you made the memory common.
BTW, when I say multithreaded I'm talking real threads and not light/green/cooperative/fake/whatever threads.
And yes, the first issue can be fixed with memory barriers. If you put a compiler+processor read barrier in front of the first line that reads the shared variable the compiler will re-read it from memory back into a register. After that it will use the register as to not kill performance until it hits the barrier again.
Note that volatile in Java and C# has different semantics (http://www.drdobbs.com/parallel/volatile-vs-volatile/2127014...)
My comment specifically mentions memory access being optimized away. You did read it, right ;-) ? Even has an example how debug mode made it work. There was nothing there about telling something to the processor. If anything it is wanting to tell something to the compiler...
> You need to use the fence and lock instructions to create memory barriers especially on multi core processors.
Yap definitely. But will that still prevent the compiler from optimizing away stuff from a shared memory block? Remember looking for memory barriers but didn't know if my ancient system had those exposed in pthreads library.
Debug mode tends to always work because it writes code as if everything was effectively volatile. After that it depends on how the threads/processes are scheduled on what core. Without proper synchronization it may work, may work slowly, may not work once in a blue moon or fail all the time. Current processor may run it fine and then it may stop working on future processors.
Fortunatly if you use things like mutexes then the barriers are handled for you.
Depends on the compiler if the barier intrinsics need volatile. I'll have to do some research on that.
I was declaring data structures in shared mmap-ed memory as volatile so compiler does not optimise it away. How would the compiler know not to optimise away operation between memory fences? A fence to me say something about the guarantee of the order in which operations are seen. The order wasn't the problem, write never making it to the other side was the issue...
If the compiler is smart about its memory barrier intrinsics it will make sure to reread anything after the read barrier and write everything before the write barrier. This will speed things up by not having to use volatile. Between the barriers it can cache reads and inside registers and optimize out multiple writes like normal.
If the compiler can't do that then you need to use volatile. Volatile was meant for I/O where every read has to hit the bus. So if you're forced to use it then you just copy the volatile variable into a non volatile one for use inside your mutex to get some speed back.
It's nearly banned from the Linux kernel. It gets used in one place, for the clock tick counter.
There is one other good use: suppression of compiler bugs.
The justification for not using it for memory-mapped IO is that you are supposed to use accessor functions. Volotile still has uses for implementing those accesor functions (with care taken for the other concerns; and, by there nature, these functions are architecture dependent anyway), or when you are not using a kernel.
[0] https://www.kernel.org/doc/Documentation/volatile-considered...
On x86 the MTTR/PAT mechanisms is what disables caching, write combining and enables write through to MMIO address ranges.
You could make volatile work, probably, by putting inline assembly both before and after it. In that case you could probably even skip marking things valatile, because the inline assembly will make the compiler behave. Why bother? This is more complicated than just doing the access from assembly.
MTRR/PAT is often not enough, or it would only be enough if you killed performance elsewhere. (different types of MMIO in the same page) It's also not something you can rely on if you want to write portable code.
Why do you think volatile won't work for MMIO with the proper MTRR/PAT setup? The volatile keeps the compiler for messing with the read/writes and the MTRR/PAT makes the processor perform all reads and writes exactly in the order the instructions tell it to without any caching or write combining.
"This is more complicated than just doing the access from assembly." and "It's also not something you can rely on if you want to write portable code." So in one paragraph you're telling me to do it in assembly and in another you want portable code.
The linux kernel has gone above and beyond using volatile but in embedded it's how IO is done when using C.
MTRR/PAT won't save you, at least without a huge performance loss, when you want write combining (etc.) for most of the addresses in a page of address space but not all of them.
MTRR/PAT won't save you from reordering that happens in a PCI bridge. It's allowed and it is done.
If a device has registers that are sensitive to multiple read/writes of the same address and ones that need caching and write combining for performance in the same 4K page then it was designed poorly. :)
PCI bridges need to support legacy mode. Otherwise DOS programs and drivers wouldn't run correctly. The BIOS sets up a compatibility MTRR setup for this reason and you can see the Linux kernel print it out on boot.
So I'm basically agreeing and disagreeing with you on this one. Once the hardware gets past a certain complexity you need to move from volatile to accessor functions/macros.
I have been bit by not having volatile modifiers for data residing in shared memory. Funny thing (and retrospectively obvious) is, it worked in debug mode, but in production data was seen by the other process. It was a fun exercise to find it one.