回答这个问题让我觉得有些事情对我来说还不清楚.我们首先假设我们从这篇文章和这篇文章中读到了所有内容.
[开始编辑]也许它不是那么明显(意大利幽默?!)但标题只是非常具有挑衅性:当然应该有一个原因,如果volatile
已经包含在C#中,我只是无法理解确切的一个.[结束编辑]
总之,我们知道我们有三个工具来共享线程之间的变量:
lock
因为这会阻止指令重新排序.
volatile
因为将强制CPU始终从内存中读取值(然后不同的CPU /内核不会缓存它,并且它们将看不到旧值).
互锁操作(Increment
/ Decrement
和CompareExchange
)因为它们将在单个原子(比例如,易失+锁定)操作中执行更改+赋值.
我不明白(C#规格参考将不胜感激):
锁如何防止缓存问题?它是否隐含在关键部分的内存屏障?
挥发性变量不能是本地的(我从Eric Lippert那里读到了一些关于此的内容,但我现在找不到这个帖子而且我不记得他的评论 - 说实话 - 我甚至都不太了解它).这让我觉得他们没有Interlocked.CompareExchange()
与朋友一起实施,他们与众不同?
volatile
例如,在此代码中,修饰符将执行什么操作?
volatile int _volatileField = 0; int _normalField = 0; void test() { Interlocked.Increment(ref _normalField); ++_volatileField; }
[开始编辑]前面的例子涉及原子读取+写入,让我们改变它_volatileField = 1;
,在这里我不是在谈论原子操作.[结束编辑]
此外,什么编译器(旁边的警告)将在这里做:
Interlocked.Increment(ref _volatileField);
它们似乎是完全不同的东西(正如我想象的那样)但是对于我的理解Interlocked.Increment()
操作数应该隐含地是易变的(然后它将仅添加原子增量).非易失性领域怎么可能?他们也暗示了障碍吗?这不会影响性能(与易失性相比)吗?
如果volatile
不暗示障碍但其他人不这样做那么为什么我们不能将它们用作局部变量?特别是当在例如并行循环中使用时,这将以显着的方式损害性能(我正在考虑具有很少代码的小函数,这些代码适用于可以很好地使用数据高速缓存的大量数据).
[开始编辑]我发现以前的句子真的不清楚(抱歉我的英文).我的意思是:如果性能(的volatile
相比CompareExchange
,这里比较适用)更好的(是的,我们可以测量并在某些情况下不同的是可测量的,可见的),那么,为什么我们不能为局部变量使用它们?我正在考虑操纵大量数据的并行循环(其中开销和障碍可能会对性能造成很大影响).[结束编辑]
这个问题非常令人困惑.让我试着打破它.
volatile变量有用吗?
是.C#团队不会添加无用的功能.
如果是的话那么?
易失性变量在某些对性能敏感的多线程应用程序中很有用,其中应用程序体系结构基于跨线程共享内存.
除了编辑之外,我注意到普通的业务线C#程序员在这些情况下都应该很少见.首先,我们在这里讨论的性能特征大约是几十纳秒; 大多数LOB应用程序的性能要求以秒或分钟为单位,而不是以纳秒为单位.其次,大多数LOB C#应用程序只能使用少量线程来完成它们的工作.第三,共享内存是一个坏主意,是许多错误的原因; 使用工作线程的LOB应用程序不应直接使用线程,而应使用任务并行库来安全地指示工作线程执行计算,然后返回结果.考虑await
在C#5.0中使用new 关键字来促进基于任务的异步,而不是直接使用线程.
在LOB应用程序中使用volatile是一个很大的危险信号,应该由专家进行大量审查,理想情况下是为了更高级别,更少危险的做法而被淘汰.
锁定将阻止指令重新排序.
C#规范将锁描述为代码中的特殊点,以保证在进入和离开锁时以特定方式对某些特殊副作用进行排序.
volatile因为会强制CPU始终从内存中读取值(然后不同的CPU /内核不会缓存它,并且它们不会看到旧值).
您所描述的是有关如何实现volatile的实现细节; 没有要求通过放弃缓存并返回主内存来实现volatile.挥发性的要求在规范中有详细说明.
互锁操作在单个原子(快速)操作中执行更改+分配.
我不清楚为什么你在"原子"之后有"快速"括号; "fast"不是"atomic"的同义词.
锁如何防止缓存问题?
再次:锁被记录为代码中的特殊事件; 需要编译器来确保其他特殊事件具有与锁相关的特定顺序.编译器如何选择实现这些语义是一个实现细节.
它是否隐含在关键部分的内存屏障?
在实践中是的,锁会引入一个完整的围栏.
易变量不能是本地的
正确.如果从两个线程访问本地,则本地必须是特殊的本地:它可以是委托的封闭外部变量,也可以是异步块或迭代器块.在所有情况下,本地实际上都被视为一个领域.如果你想要这样的东西是易变的,那么不要使用像匿名方法,异步块或迭代器块这样的高级功能!这就是混合最高级别和最低级别的C#编码,这是一件非常奇怪的事情.编写自己的闭包类,并根据需要使字段变为volatile.
我从Eric Lippert那里读到了一些关于此的内容,但我现在找不到那篇文章而且我不记得他的回答了.
好吧,我也不记得了,所以我在搜索引擎中输入"Eric Lippert为什么局部变量不能变化".这让我想到了这个问题:
为什么局部变量不能在C#中出现波动?
也许这就是你的想法.
这让我觉得他们没有用Interlocked.CompareExchange()和朋友实现.
C#将volatile字段实现为volatile字段.易失性场是CLR中的基本概念; CLR如何实现它们是CLR的实现细节.
他们有什么不同?
我不明白这个问题.
例如,在这段代码中,volatile修饰符会做什么?
++_volatileField;
它没有任何帮助,所以不要这样做.波动性和原子性是完全不同的东西.在volatile字段上执行正常增量不会使增量成为原子增量.
此外,什么编译器(旁边的警告)将在这里做:
如果被调用的方法引入了一个fence,那么C#编译器真的应该禁止该警告.我从来没有设法进入编译器.希望有一天团队会.
volatile字段将以原子方式更新.将通过增量引入围栏,因此减少了跳过易失性半围栏的事实.
非易失性领域怎么可能?
这是CLR的实现细节.
他们也暗示了障碍吗?
是的,互锁操作会带来障碍.同样,这是一个实现细节.
这不会影响性能(与易失性相比)吗?
首先,将破损代码的性能与工作代码进行比较是浪费时间.
其次,如果你觉得浪费时间,你完全有能力衡量每个人的表现.两种方式编写代码,拿出一个秒表,每次运行一万亿次,你就会知道哪个更快.
如果volatile不会暗示障碍,但其他人会这样做,那么为什么我们不能将它们用作局部变量呢?
我甚至无法开始理解这个问题.
对于以下代码,易失性变量可能会非常有用:
while (myVolatileFlag) ...
如果myVolatileFlag
声明为a volatile bool
,则会阻止编译器缓存其值并假设它在循环期间不会更改.(但是,编写一些实际上证明了应用程序volatile
产生差异的代码实际上相当困难.)
来自http://msdn.microsoft.com/en-us/LIBRARY/x13ttww7%28v=vs.80%29.aspx
volatile关键字指示可能由多个并发执行的线程修改字段.声明为volatile的字段不受编译器优化的约束,这些优化假定由单个线程进行访问.这可确保始终在字段中显示最新值.
这是一个演示该问题的示例程序:
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(); } } }
运行上述程序的"Release"版本,它将在一秒钟后终止.volatile
从中删除修饰符volatile bool flag
,它永远不会终止.
挥发性的当地人
一般来说,本地人不需要volatile,因为编译器可以看到您是在修改本地,还是将对本地的引用传递给另一个方法.在这两种情况下,编译器都会假定该值正在更改,并将禁用依赖于不更改的值的优化.
但是,对于带有Lambda等的C#的更高版本,事情并不那么明确.请参阅Eric Lippert在此主题中的回复.