根据MSDN:
volatile关键字表示某个字段可能被同时执行的多个线程修改.声明为volatile的字段不受编译器优化的约束,这些优化假定由单个线程进行访问.这可确保始终在字段中显示最新值.
请注意最后一句:
这可确保始终在字段中显示最新值.
但是,此关键字存在问题.
我已经读过它可以改变指令的顺序:
Run Code Online (Sandbox Code Playgroud)First instruction Second instruction Can they be swapped? Read Read No Read Write No Write Write No Write Read Yes! <----
这意味着John为一个易变的字段设置了一个值,后来 Paul想要阅读该字段,Paul正在获得旧的价值!
这是怎么回事?这不是主要的工作吗?
我知道还有其他解决方案,但我的问题是关于volatile关键字.
我(作为程序员)是否需要阻止使用此关键字 - 因为这种奇怪的行为?
我读过很多关于volatile和VoletileRead(ReadAcquireFence)的矛盾信息(msdn,SO等).
我理解那些内存访问重新排序的限制含义 - 我仍然完全混淆的是新鲜度保证 - 这对我来说非常重要.
(...)这可确保始终在字段中显示最新值.
读取volatile字段称为volatile读取.易失性读取具有"获取语义"; 也就是说,保证在指令序列之后发生的任何内存引用之前发生.
public static int VolatileRead(ref int address)
{
int ret = address;
MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
return ret;
}
Run Code Online (Sandbox Code Playgroud)
根据msdn MemoryBarrier doc内存屏障阻止重新排序.然而,这似乎对新鲜度没有任何影响 - 对吗?
如何获得新鲜度保证?标记字段volatile与使用VolatileRead和VolatileWrite语义访问它之间有区别吗?我目前正在执行我的性能关键代码,需要保证新鲜度,但读者有时会得到陈旧的价值.我想知道是否标记状态不稳定将使情况不同.
EDIT1:
我正在努力实现的目标 - 保证读者线程将尽可能获得共享变量(由多个编写者编写)的最新值 - 理想情况下不会超过上下文切换或其他可能推迟立即操作的操作的成本写国家.
如果挥发性或更高级别的构造(例如锁定)具有这种保证(是吗?)而不是它们如何实现这一点?
EDIT2:
非常简洁的问题应该是 - 如何在读取过程中尽可能保证新的价值?理想情况下没有锁定(因为不需要独占访问,并且存在高争用的可能性).
从我在这里学到的东西,我想知道这可能是解决方案(求解(?)行标有注释):
private SharedState _sharedState;
private SpinLock _spinLock = new SpinLock(false);
public void Update(SharedState newValue) …Run Code Online (Sandbox Code Playgroud) 像许多其他人一样,我一直对易失性读/写和围栏感到困惑.所以现在我想完全理解这些是做什么的.
因此,易失性读取应该(1)表现出获取语义,(2)保证读取的值是新鲜的,即它不是缓存值.让我们关注(2).
现在,我已经读过,如果你想执行一个易失性读取,你应该在读取后引入一个获取栅栏(或一个完整的栅栏),如下所示:
int local = shared;
Thread.MemoryBarrier();
Run Code Online (Sandbox Code Playgroud)
这究竟是如何阻止读取操作使用以前缓存的值?根据栅栏的定义(不允许读取/存储在栅栏上方/下方移动),我会在读取之前插入栅栏,防止读取穿过栅栏并及时向后移动(也就是说,是缓存).
如何防止读取被及时向前移动(或后续指令被及时向后移动)保证了易失性(新鲜)读取?它有什么用?
类似地,我认为易失性写入应该在写入操作之后引入栅栏,从而阻止处理器及时向前移动写入(也就是说,延迟写入).我相信这会使处理器刷新对主存储器的写入.
但令我惊讶的是,C#实现在写入之前引入了栅栏!
[MethodImplAttribute(MethodImplOptions.NoInlining)] // disable optimizations
public static void VolatileWrite(ref int address, int value)
{
MemoryBarrier(); // Call MemoryBarrier to ensure the proper semantic in a portable way.
address = value;
}
Run Code Online (Sandbox Code Playgroud)
更新
根据这个例子,显然是从"坚果壳中的C#4"中取出的,在写入之后放置的栅栏2 应该强制写入立即刷新到主存储器,并且在读取之前放置的栅栏3 应该保证一个新的阅读:
class Foo{
int _answer;
bool complete;
void A(){
_answer = 123;
Thread.MemoryBarrier(); …Run Code Online (Sandbox Code Playgroud) 我们都知道堆栈和堆的想法,但我最近读到了第三个保存数据的选项:寄存器.
我很难找到关于这种类型的好文章,我发现的是:http://www.dotnetperls.com/method-parameter,以及C的很多内容,例如:http://igoro.com/存档/易失性-关键字在-C-存储器模型解释的/
到目前为止我唯一的实际信息:每个CPU都有自己的寄存器,可用于保存数据,以尽可能快的方式访问,例如在for循环中.
据我所见,这种注册是由CLR完成的.然后我想起了这个volatile-keyword,如果我们看一下MSDN:
volatile关键字表示某个字段可能被同时执行的多个线程修改.声明为volatile的字段不受编译器优化的约束,这些优化假定由单个线程进行访问.这可确保始终在字段中显示最新值.
那么Volatile也是如此吗?它告诉CLR不要使用CPU寄存器而是堆栈/堆,它可以被所有CPU /线程访问?
我很抱歉这个令人困惑的问题,但关于这个话题的信息确实很少.
所以我现在研究这个主题很长一段时间了,我想我理解最重要的概念,如发布和获取内存栅栏.
但是,我还没有找到令人满意的解释,volatile主存储器的缓存和缓存之间的关系.
因此,我理解对volatile字段的每次读写操作都会对读取以及之前和之后的写入操作执行严格的排序(读取 - 获取和写入 - 释放).但这只能保证操作的顺序.它没有说明这些更改对其他线程/处理器可见的时间.特别是,这取决于刷新缓存的时间(如果有的话).我记得曾读过Eric Lippert的评论,他说" volatile字段的存在会自动禁用缓存优化".但我不确定这究竟是什么意思.这是否意味着整个程序的缓存完全被禁用,因为我们在volatile某处有一个字段?如果不是,禁用缓存的粒度是多少?
此外,我读到了一些关于强和弱的易失性语义的东西,并且C#遵循强大的语义,即每次写入都将直接进入主存储器,无论它是否是一个volatile字段.我对这一切感到非常困惑.
众所周知,与Java的易失性不同,.NET的版本允许使用来自另一个位置的以下易失性读取来重新排序易失性写入.当它是一个问题时 MemoryBarier,建议放在它们之间,或者Interlocked.Exchange可以用来代替volatile写.
它可以工作,但MemoryBarier在高度优化的无锁代码中使用时可能成为性能杀手.
我想了一下,想出了一个主意.我希望有人告诉我,我是否采取了正确的方式.
所以,这个想法如下:
我们希望防止这两种访问之间的重新排序:
volatile1 write
volatile2 read
Run Code Online (Sandbox Code Playgroud)
从.NET MM我们知道:
1) writes to a variable cannot be reordered with a following read from
the same variable
2) no volatile accesses can be eliminated
3) no memory accesses can be reordered with a previous volatile read
Run Code Online (Sandbox Code Playgroud)
为了防止写入和读取之间不必要的重新排序,我们从刚刚写入的变量中引入了一个虚拟易失性读取:
A) volatile1 write
B) volatile1 read [to a visible (accessible | potentially shared) location]
C) volatile2 read
Run Code Online (Sandbox Code Playgroud)
在这种情况下,B不能用A重新排序,因为它们都访问相同的变量, C不能用B重新排序,因为两个易失性读取不能相互重新排序,并且传递C …
您将使用volatile关键字的具体情况是什么?更重要的是:该计划如何从中受益?
从我已经阅读和了解到的内容:volatile应该用于不同线程访问的变量,因为它们比非易失性线程读取的速度稍快.如果是这样,是否应该有一个关键字强制执行相反的操作?
或者他们实际上是在所有线程之间同步?正常变量怎么样?
我有很多多线程代码,我想稍微优化一下.当然我不希望获得巨大的性能提升(无论如何我都没有任何问题),但我总是试图让我的代码更好.我对这个关键字感到有些困惑.
我有一个关于C#/ .NET中法律指令重新排序的问题.
让我们从这个例子开始吧.我们在某个类中定义了此方法,其中_a,_b和_c是字段.
int _a;
int _b;
int _c;
void Foo()
{
_a = 1; // Write 1
_b = 1; // Write 2
_c = 1; // Write 3
}
Run Code Online (Sandbox Code Playgroud)
我们的呼叫环境看起来像这样.
//memory operations
ClassInstance.Foo();
//memory operations
Run Code Online (Sandbox Code Playgroud)
我想知道当这个方法调用内联对函数调用时,什么样的法律指令重新排序是可能的.更具体地说,我想知道在Foo()中重新排序内存操作是否/何时合法,内存操作在其外部(来自我们之前的示例,//内存操作).
此外,函数调用(无内联)在某种意义上是"生成内存障碍".同样,在函数调用之前或之后发生的内存操作不能与函数调用中的内存操作重新排序.
如果是这样,当它被编译器内联时,它仍会有这种"内存屏障"行为吗?
fallowing子句来自jetbrains.net在阅读了这篇以及网上的其他文章后,我仍然不明白在第一个线程进入锁之后如何返回null.有人确实理解它可以帮助我并以更人性化的方式解释它吗?
"考虑以下代码:
public class Foo
{
private static Foo instance;
private static readonly object padlock = new object();
public static Foo Get()
{
if (instance == null)
{
lock (padlock)
{
if (instance == null)
{
instance = new Foo();
}
}
}
return instance;
}
};
Run Code Online (Sandbox Code Playgroud)
给定上面的代码,初始化Foo实例的写入可以被延迟,直到写入实例值,从而产生实例返回处于单元化状态的对象的可能性.
为了避免这种情况,必须使实例值易变."
c# ×8
volatile ×6
.net ×5
concurrency ×2
memory-model ×2
caching ×1
clr ×1
java ×1
performance ×1
singleton ×1