问题
Answering this question made me think about something is still not clear for me. Let's first assume we read all from this post and this post.
[begin edit] Maybe it's not so obvious (Italian humor?!) but title is just pretty provocative: of course there should be a reason if volatile
has been included in C#, I just can't understand exact one.[end edit]
In short we know we have three tools to share a variable between threads:
lock
because this will prevent instruction reordering.volatile
because will force CPU to always read value from memory (then different CPUs/cores won't cache it and they won't see old values).- Interlocked operations (
Increment
/Decrement
andCompareExchange
) because they'll perform change + assignment in a single atomic (fastfaster than, for example, a volatile + lock) operation.
What I don't understand (C# specs reference would be appreciated):
- How lock will prevent cache problem? Is it implicit a memory barrier in a critical section?
- Volatile variables can't be local (I read something from Eric Lippert about this but I can't find that post now and I don't remember his comments and - to be honest - I didn't even understand it very well). This makes me think they're not implemented with an
Interlocked.CompareExchange()
and friends, in what they're different?
What volatile
modifier will do for example in this code?
volatile int _volatileField = 0;
int _normalField = 0;
void test()
{
Interlocked.Increment(ref _normalField);
++_volatileField;
}
[begin edit] previous example involves atomic read + write, let's change it to _volatileField = 1;
, here I'm not talking about atomic operations. [end edit]
Moreover what compiler (beside warnings) will do here:
Interlocked.Increment(ref _volatileField);
They seems pretty different things (as I would imagine they are) but for my understanding Interlocked.Increment()
operand should implicitly be volatile (then it'll add just atomic increment). How is it possible for non volatile fields? Does they imply barriers too? Doesn't this hurt performance a lot (compared to volatile)?
If volatile
doesn't imply barriers but others do then why we can't use them as on local variables? Especially when used in, for example, parallel loops this will hurt performance in a significant way (I'm thinking about small functions with little code that work on a lot of data where data cache may be well used).
[begin edit] I found previous sentence was really unclear (sorry for my English). What I mean is: if performance (of volatile
compared to CompareExchange
, where comparison is applicable) are better (yes we can measure and in some scenario difference is measurable and visible) then why we can't use them for local variables? I'm thinking about parallel loops that manipulates a lot of data (where both overhead and barriers may hurt performance a lot).[end edit]
回答1:
This question is very confusing. Let me try to break it down.
Are volatile variables useful?
Yes. The C# team would not have added a useless feature.
If yes then when?
Volatile variables are useful in certain highly performance-sensitive multithreaded applications where the application architecture is predicated on sharing memory across threads.
As an editorial aside, I note that it should be rare for normal line-of-business C# programmers to be in any of these situations. First, the performance characteristics we are talking about here are on the order of tens of nanoseconds; most LOB applications have performance requirements measured in seconds or minutes, not in nanoseconds. Second, most LOB C# applications can do their work with only a small number of threads. Third, shared memory is a bad idea and a cause of many bugs; LOB applications which use worker threads should not use threads directly, but rather use the Task Parallel Library to safely instruct worker threads to perform calculations, and then return the results. Consider using the new await
keyword in C# 5.0 to facilitate task-based asynchrony, rather than using threads directly.
Any use of volatile in a LOB application is a big red flag and should be heavily reviewed by experts, and ideally eliminated in favour of a higher-level, less dangerous practice.
lock will prevent instruction reordering.
A lock is described by the C# specification as being a special point in the code such that certain special side effects are guaranteed to be ordered in a particular way with respect to entering and leaving the lock.
volatile because will force CPU to always read value from memory (then different CPUs/cores won't cache it and they won't see old values).
What you are describing is implementation details for how volatile could be implemented; there is not a requirement that volatile be implemented by abandoning caches and going back to main memory. The requirements of volatile are spelled out in the specification.
Interlocked operations perform change + assignment in a single atomic (fast) operation.
It is not clear to me why you have parenthesized "fast" after "atomic"; "fast" is not a synonym for "atomic".
How lock will prevent cache problem?
Again: lock is documented as being a special event in the code; a compiler is required to ensure that other special events have a particular order with respect to the lock. How the compiler chooses to implement those semantics is an implementation detail.
Is it implicit a memory barrier in a critical section?
In practice yes, a lock introduces a full fence.
Volatile variables can't be local
Correct. If you are accessing a local from two threads then the local must be a special local: it could be a closed-over outer variable of a delegate, or in an async block, or in an iterator block. In all cases the local is actually realized as a field. If you want such a thing to be volatile then do not use high-level features like anonymous methods, async blocks or iterator blocks! That is mixing the highest level and the lowest level of C# coding and that is a very strange thing to do. Write your own closure class and make the fields volatile as you see fit.
I read something from Eric Lippert about this but I can't find that post now and I don't remember his answer.
Well I don't remember it either, so I typed "Eric Lippert Why can't a local variable be volatile" into a search engine. That took me to this question:
why can't a local variable be volatile in C#?
Perhaps that is what you're thinking of.
This makes me think they're not implemented with an Interlocked.CompareExchange() and friends.
C# implements volatile fields as volatile fields. Volatile fields are a fundamental concept in the CLR; how the CLR implements them is an implementation detail of the CLR.
in what they're different?
I don't understand the question.
What volatile modifier will do for example in this code?
++_volatileField;
It does nothing helpful, so don't do that. Volatility and atomicity are completely different things. Doing a normal increment on a volatile field does not make the increment into an atomic increment.
Moreover what compiler (beside warnings) will do here:
The C# compiler really ought to suppress that warning if the method being called introduces a fence, as this one does. I never managed to get that into the compiler. Hopefully the team will someday.
The volatile field will be updated in an atomic manner. A fence will be introduced by the increment, so the fact that the volatile half-fences are skipped is mitigated.
How is it possible for non volatile fields?
That's an implementation detail of the CLR.
Does they imply barriers too?
Yes, the interlocked operations introduce barriers. Again, this is an implementation detail.
Doesn't this hurt performance a lot (compared to volatile)?
First off, comparing the performance of broken code to working code is a waste of time.
Second, if you do feel like wasting time, you are perfectly capable of measuring the performance of each yourself. Write the code both ways, get out a stopwatch, run it a trillion times each way, and you'll know which is faster.
If volatile doesn't imply barriers but others do then why we can't use them as on local variables?
I can't even begin to make sense of this question.
回答2:
Volatile variables could theortically be useful with code like the following:
while (myVolatileFlag)
...
If myVolatileFlag
is declared as a volatile bool
, it will prevent the compiler from caching its value and assuming that it won't change during the loop. (However, it's actually rather hard to write some code that actually demonstrates the difference that applying volatile
makes.)
From http://msdn.microsoft.com/en-us/LIBRARY/x13ttww7%28v=vs.80%29.aspx
The volatile keyword indicates that a field might be modified by multiple concurrently executing threads. Fields that are declared volatile are not subject to compiler optimizations that assume access by a single thread. This ensures that the most up-to-date value is present in the field at all times.
Here's an example program that demonstrates the issue:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Demo
{
internal class Program
{
private void run()
{
Task.Factory.StartNew(resetFlagAfter1s);
int x = 0;
while (flag)
++x;
Console.WriteLine("Done");
}
private void resetFlagAfter1s()
{
Thread.Sleep(1000);
flag = false;
}
private volatile bool flag = true;
private static void Main()
{
new Program().run();
}
}
}
Run a "Release" build of the above program, and it will terminate after one second. Remove the volatile
modifier from volatile bool flag
, and it will never terminate.
Volatile Locals
Generally speaking, volatile is not needed for locals because the compiler can see if you are modifying a local, or are passing a reference to the local to another method. In both cases the compiler will assume that the value is being changed, and will disable optimizations that depend on the value not changing.
However, with later versions of C# with Lambdas etc, things aren't quite so clear-cut. See the reply from Eric Lippert in this thread.
回答3:
How
lock
will prevent cache problem? Is it implicit a memory barrier in a critical section?
Yes, lock
also acts as a full fence (it has both acquire and release semantics). This page on the Windows dev center explains how this works:
Processors can support instructions for memory barriers with acquire, release, and fence semantics. These semantics describe the order in which results of an operation become available. With acquire semantics, the results of the operation are available before the results of any operation that appears after it in code. With release semantics, the results of the operation are available after the results of any operation that appears before it in code. Fence semantics combine acquire and release semantics. The results of an operation with fence semantics are available before those of any operation that appears after it in code and after those of any operation that appears before it.
Volatile variables can't be local (I read something from Eric Lippert about this but I can't find that post now and I don't remember his answer). This makes me think they're not implemented with an Interlocked.CompareExchange() and friends, in what they're different?
What volatile modifier will do for example in this code?
They are different in that they do not prevent race conditions involving other threads operating on the same memory. A read/modify/store involving a volatile
field will not be atomic as a whole even though each of the three steps will be (C# has chosen to guarantee atomicity for volatile reads and writes).
The volatile
on the sample code won't do much. It will make certain that when the read/modify/store sequence for incrementing _volatileField
begins the read will actually go to memory instead of being possibly satisfied from the processor cache, but it will not help at all with race conditions if there are other threads that concurrently write to the field.
来源:https://stackoverflow.com/questions/20348697/are-volatile-variables-useful-if-yes-then-when