RE: When is "volatile" used instead of "lock" ?
- From: Jon Skeet [C# MVP] <skeet@xxxxxxxxx>
- Date: Thu, 14 Jun 2007 21:50:49 +0100
Peter Ritchie [C# MVP] <PRSoCo@xxxxxxxxxxxxxxxxx> wrote:
Acquiring a lock has acquire semantics, and releasing a lock has
release semantics. You don't need any volatility if all access to any
particular item of shared data is always made having acquired a certain
lock.
...which only applies to reference types. Most of this discussion has been
revolving around value types (by virtue of Interlocked.Increment), for which
"lock" cannot not apply. e.g. you can't switch from using lock on a member
to using Interlocked.Increment on that member, one works with references and
the other with value types (specifically Int32 and Int64). This is what
raised my concern.
It's not a case of using a lock on a particular value - taking the lock
out creates a memory barrier beyond which *no* reads can pass, not just
reads on the locked expression.
It certainly *is* documented. ECMA 335, section 12.6.5:
<quote>
Acquiring a lock (System.Threading.Monitor.Enter or entering a
synchronized method) shall implicitly
perform a volatile read operation, and releasing a lock
(System.Threading.Monitor.Exit or leaving a
synchronized method) shall implicitly perform a volatile write
operation.
</quote>
...still doesn't document anything about the members/variables within the
locked block (please read my example). That quote applies only to the
reference used as the parameter for the lock.
There can be no lock acquire semantics for value members. Suggesting
"locking appropriately" cannot apply here and can be misconstrued by some
people by creating something like "lock(myLocker){intMember = SomeMethod();}"
which does not do the same thing as making intMember volatile, increases
overhead needlessly, and still leaves a potential bug.
No, it *doesn't* leave a bug - you've misunderstood the effect of lock
having acquire semantics.
volatile and lock should be used in conjunction, one is not a replacement
for the other.
If you lock appropriately, you never need to use volatile.
Even if the discussion hasn't been about value types, a dangerous statement;
because it could only apply to reference types (i.e. if myObject is wrapped
with lock(myObject) in every thread, yes I don't need to declare it with
volatile--but that's probably not why I'm using lock). In the context of
reference types, volatile only applies to the pointer (reference) not
anything within the object it references. Reference assignment is atomic,
there's no need to use lock to guard that sort of thing. You use lock to
guard a non-atomic invariant, volatile has nothing to do with that--it has to
do with the optimization (ordering, caching) of pointer/value reads and
writes.
Atomicity and volatility are very different things, and shouldn't be
confused.
Locks do more than just guarding non-atomic invariants though - they
have the acquire/release semantics which make volatility unnecessary.
To be absolutely clear on this, if I have:
int someValue;
object myLock;
....
lock (myLock)
{
int x = someValue;
someValue = x+1;
}
then the read of someValue *cannot* be from a cache - it *must* occur
after the lock has been taken out. Likewise before the lock is
released, the write back to someValue *must* have been made effectively
flushed (it can't occur later than the release in the logical memory
model).
Here's how that's guaranteed by the spec:
"Acquiring a lock (System.Threading.Monitor.Enter or entering a
synchronized method) shall implicitly perform a volatile read
operation"
and
"A volatile read has =3Facquire semantics=3F meaning that the read is
guaranteed to occur prior to any references to memory that occur after
the read instruction in the CIL instruction sequence."
That means that the volatile read due to the lock is guaranteed to
occur prior to the "reference to memory" (reading someValue) which
occurs later in the CIL instruction sequence.
The same thing happens the other way round for releasing the lock.
Calling Monitor.Enter/Minitor.Exit is a pretty heavy-weight means of
ensuring acquire semantics; at least 5 times slower if volatile is all you
need.
But still fast enough for almost everything I've ever needed to do, and
I find it a lot easier to reason about a single way of doing things
than having multiple ways for multiple situations. Just a personal
preference - but it definitely *is* safe, without ever needing to
declare anything volatile.
--
Jon Skeet - <skeet@xxxxxxxxx>
http://www.pobox.com/~skeet Blog: http://www.msmvps.com/jon.skeet
If replying to the group, please do not mail me too
.
- Follow-Ups:
- Re: When is "volatile" used instead of "lock" ?
- From: Willy Denoyette [MVP]
- Re: When is "volatile" used instead of "lock" ?
- References:
- RE: When is "volatile" used instead of "lock" ?
- From: Jon Skeet [C# MVP]
- RE: When is "volatile" used instead of "lock" ?
- Prev by Date: Re: int n = default(System.Int32);
- Next by Date: WebServices exception
- Previous by thread: Re: When is "volatile" used instead of "lock" ?
- Next by thread: Re: When is "volatile" used instead of "lock" ?
- Index(es):
Relevant Pages
|
Loading