I wonder whether you have misunderstood what volatile
means. Volatile can be used with types that can be read or written as an atomic action.
There is no acquire/release of a lock, only a barrier to compile-time and run-time reordering to provide lockless aquire/release semantics (https://preshing.com/20120913/acquire-and-release-semantics/). On non-x86 this may require barrier instructions in the asm, but not taking a lock.
volatile
indicates that a field may be modified by other threads, which is why read/writes need to be treated as atomic and not optimised.
Your question is a little ambiguous.
1/ If you mean, will the compiler transform:
var local = something;
if (local != null) local.DoThings();
into:
if (something != null) something.DoThings();
then the answer is no.
2/ If you mean, will "DoThings()
" be called twice on the same object:
var local = something;
if (local != null) local.DoThings();
if (something != null) something.DoThings();
then the answer is mostly yes, unless another thread has changed the value of "something
" before the second "DoThings()
" is invoked. If this is the case then it could give you a run time error - if after the "if
" condition is evaluated and before "DoThings
" is called, another thread sets "something
" to null
then you will get a runtime error. I assume this is why you have your "var local = something;
".
3/ If you mean will the following cause two reads:
if (something != null) something.DoThings();
then yes, one read for the condition and a second read when it invokes DoThings()
(assuming that something
is not null). Were it not marked volatile
then the compiler might manage that with a single read.
In any event the implementation of the function "DoThings()
" needs to be aware that it could be called by multiple threads, so would need to consider incorporating a combination of locks and its own volatile members.
atomic
if things like that could happen, so I think you're mostly just asking whether that's actually standardized somewhere the wayvolatile
is in C++ (where each read/write is considered an observable side-effect that optimization must preserve), or whether useful implementations just always work that way. I don't know C#, but does++aNumber
do an atomic RMW, or is it an atomic load and a separate atomic store? – Nitrileif
block. – Moltkevolatile
in that respect (plus other semantics that C++ doesn't have), not C++11atomic
. But yes, I see the extra write which wouldn't happen at all with the original. And also one fewer read, so the write is always storing1
regardless of racing with other threads. Unlike with++aNumber
, where another thread could have changedaNumber
between the zero-check and the read that's part of the increment. (Well, until you simplified it with your last edit). – Nitrile++
is not an atomic operation, butvolatile
is only allowed to be declared with types capable of atomic reads and writes. – Moltke