Monday 19 August 2019

multithreading - Does the C++ volatile keyword introduce a memory fence?



I understand that volatile informs the compiler that the value may be changed, but in order to accomplish this functionality, does the compiler need to introduce a memory fence to make it work?



From my understanding, the sequence of operations on volatile objects cannot be reordered and must be preserved. This seems to imply some memory fences are necessary and that there isn't really a way around this. Am I correct in saying this?







There is an interesting discussion at this related question



Jonathan Wakely writes:




... Accesses to distinct volatile variables cannot be reordered by the
compiler as long as they occur in separate full expressions ... right
that volatile is useless for thread-safety, but not for the reasons he
gives. It's not because the compiler might reorder accesses to

volatile objects, but because the CPU might reorder them. Atomic
operations and memory barriers prevent the compiler and the CPU from
reordering




To which David Schwartz replies in the comments:




... There's no difference, from the point of view of the C++ standard,
between the compiler doing something and the compiler emitting

instructions that cause the hardware to do something. If the CPU may
reorder accesses to volatiles, then the standard doesn't require that
their order be preserved. ...



... The C++ standard doesn't make any distinction about what does the
reordering. And you can't argue that the CPU can reorder them with no
observable effect so that's okay -- the C++ standard defines their
order as observable. A compiler is compliant with the C++ standard on
a platform if it generates code that makes the platform do what the
standard requires. If the standard requires accesses to volatiles not

be reordered, then a platform the reorders them isn't compliant. ...



My point is that if the C++ standard prohibits the compiler from
reordering accesses to distinct volatiles, on the theory that the
order of such accesses is part of the program's observable behavior,
then it also requires the compiler to emit code that prohibits the CPU
from doing so. The standard does not differentiate between what the
compiler does and what the compiler's generate code makes the CPU do.





Which does yield two questions: Is either of them "right"? What do actual implementations really do?


Answer



Rather than explaining what volatile does, allow me to explain when you should use volatile.




  • When inside an signal handler. Because writing to a volatile variable is pretty much the only thing the standard allows you to do from within a signal handler. Since C++11 you can use std::atomic for that purpose, but only if the atomic is lock-free.

  • When dealing with setjmp according to Intel.

  • When dealing directly with hardware and you want to ensure that the compiler does not optimize your reads or writes away.




For example:



volatile int *foo = some_memory_mapped_device;
while (*foo)
; // wait until *foo turns false


Without the volatile specifier, the compiler is allowed to completely optimize the loop away. The volatile specifier tells the compiler that it may not assume that 2 subsequent reads return the same value.



Note that volatile has nothing to do with threads. The above example is does not work if there was a different thread writing to *foo because there is no acquire operation involved.




In all other cases, usage of volatile should be considered non-portable and not pass code review anymore except when dealing with pre-C++11 compilers and compiler extensions (such as msvc's /volatile:ms switch, which is enabled by default under X86/I64).


No comments:

Post a Comment

php - file_get_contents shows unexpected output while reading a file

I want to output an inline jpg image as a base64 encoded string, however when I do this : $contents = file_get_contents($filename); print &q...