Ben Voigt said:
Let's look at the Win32 declaration for an Interlocked function:
LONG InterlockedExchange(
LONG volatile* Target,
LONG Value
);Clearly, Target is intended to be the address of a volatile variable.
Sure, you can pass a non-volatile pointer, and there is an implicit
conversion, but if you do *the variable will be treated as volatile only
inside InterlockedExchange*. The compiler can still do anything outside
InterlockedExchange, because it is dealing with a non-volatile variable.
Sure, but this was not my point, the point is that Interlocked operations
imply barriers, all or not full. "volatile" implies full barriers, so they
both imply barriers, but they serve different purposes. One does not exclude
the other, but that doesn't mean they should always be used in tandem, all
depends on what you want to achieve in your code, what guarantees you want.
Anyway, the docs do not impose it, the C# docs on Interlocked don't even
mention volatile, and the Win32 docs (Interlocked API's) don't spend a word
on the volatile argument. (note that the volatile was added to the
signature after NT4 SP1).
And, it can't possibly change behavior when InterlockedExchange is called,
because the call could be made from a different library, potentially not
yet loaded.
Sorry but you are mixing native code and managed code semantics. What I
mean, is that the semantics of the C (native) volatile is not the same as
the semantics of C# 'volatile'. So when I refered to C++ supporting
"volatile" I was refering to managed dialects (VC7.x and VC8) who's volatile
semantics are obviously the same as all other languages
I don't wanna discuss the semantics of volatile in standard C/c++ here, they
are so imprecise that IMO it will lead to an endless dicussion, not relevant
to C#.
Also I don't wanna discuss the semantics of Win32 Interlocked either, "Win32
interlocked API's" do accept pointers to volatile items, while .NET does
accept "volatile pointers" (in unsafe context) as arguments of a method
call, but treats the item as non volatile. Also, C#, will issue a warning
when passing a volatile field (passed by ref is required by Interlocked
operations), that means that the item will be treated as volatile, but the
reference itself will not.
Consider this:
/* compilation unit one */
void DoIt(LONG *target)
{
LONG value = /* some long calculation here */;
if (value != InterlockedExchange(target, value))
{
/* some complex operation here */
}
}
/* compilation unit two */
extern void DoIt(LONG * target);
extern LONG shared;
void outer(void)
{
for( int i = 0; i < 1000; i++ )
{
DoIt(&shared);
}
}
Now, clearly, the compiler has no way of telling that DoIt uses
Interlocked access, since DoIt didn't declare volatile semantics on the
pointer passed in. So the compiler can, if desired, transform outer
thusly:
void outer(void)
{
LONG goodLocalityOfReference = shared;
for( int i = 0; i < 1000; i++ )
{
DoIt(&goodLocalityOfReference);
}
shared = goodLocalityOfReference;
}
Except for one thing. In native code, pointers have values that can be
compared, subtracted, etc. So the compiler has to honestly pass the
address of shared. In managed code, with tracking handles, the compiler
doesn't have to preserve the address of the variable (that would, after
all, defeat compacting garbage collection). Oh, sure, the JIT has a lot
more information about what is being called than a native compiler does,
it almost gets rid of separate compilation units.... but not quite. With
dynamically loaded assemblies and reflection in the mix, it is just a
helpless as a "compile-time" compiler.
I'm fairly sure that the current .NET runtime doesn't actually do any such
optimization as I've described. But I wouldn't bet against such things
being added in the future, when NUMA architectures become so widespread
that the compiler has to optimize for them.
Be safe, use volatile on every variable you want to act volatile, which
includes every variable passed to Interlocked.
VC++, all versions, and all other PC compilers that I'm aware of (as in,
not embedded), support volatile to the extent needed to invoke an
interlocked operation. That is, the real variable is always accessed at
the time specified by the compiler. The memory fences are provided by the
implementation of Interlocked*, independent of the compiler version.
Where in the docs (MSDN Platform SDK etc..) do they state that Interlocked
should always be on volatile items?
You are claiming that you should almost never use lock free techniques,
and thus volatile should be rare. This hardly contradicts my statement
that volatile should always be used in lock free programming.
Kind of, I'm claiming that you should rarely use lock-free techniques when
using C# in mainstream applications, I've seen too many people trying to
implement lock free code, and if you ask "why", the answer is mostly
"performance", and if you asked if the measured their "locked "
implementation, the answer is mostly, well I have no 'locked'
implementation, this is what I call "premature optimization" without any
guarantees, other than probably producing unrealiable code, which is (IMO)
more important than performant code .
IMO the use of volatile should be rare in the sense that you better use
locks and only use volatile for the most simple cases (which doesn't imply
'rare'), for instance when you need to guarantee that all possible observers
of a field (of type accepted by volatile) see the same value when that value
has been written to by another observer.
Remember "volatile" is something taken care of by the JIT, all it does is
eliminate some of the possible optimizations like (but not restricted to):
- volatile items cannot be registered...
- multiple stores cannot be suppressed...
- re-ordering is restricted.
- ...
But keep in mind that, 'volatile' suppresses optimizations for all possible
accesses, even when not subject to multiple observers (threads), and that
volatile fields accesses can move, some people think they can't....
Willy.