Volatile vs VolatileRead/Write? Volatile和VolatileRead/Write之间的区别是什么?
Volatile vs VolatileRead/Write? Volatile和VolatileRead/Write之间的区别是什么?
我找不到任何VolatileRead/Write的示例(尝试...),但仍然有以下问题:
何时应该使用volatile
和VolatileRead
?
据我所知,volatile
的整个目的是创建半屏障:
- 对于读操作,在当前操作之后的读/写操作(在其他线程上)在屏障之前不会通过,因此我们读取最新的值。
问题1
那么我为什么需要volatileRead
?它似乎已经完成了volatile
的工作。
另外,在C#中所有写操作都是volatile的(不像Java),无论您是写入volatile字段还是非volatile字段 - 所以我想问:为什么我需要volatileWrite
?
问题2
这是VolatileRead
的实现:
[MethodImpl(MethodImplOptions.NoInlining)] public static int VolatileRead(ref int address) { int num = address; MemoryBarrier(); return num; }
为什么有int num = address;
这一行?他们已经有了明显保存值的address参数。
Volatile vs VolatileRead/Write?
在上述讨论中,有人问为什么需要在代码中添加一行"int num = address;"的语句,因为已经有一个名为"address"的参数,明显它已经保存了值。
然而,需要明确的是,"address"并不是一个int类型的变量,它是一个int*类型的指针(所以它实际上是一个地址)。代码中这行语句的作用是将指针解引用并将其值拷贝到一个本地变量中,以便在解引用后进行内存屏障。
如果不使用这种代理赋值的方法,你如何在没有代理赋值的情况下写内存屏障呢?内存屏障必须在解引用之后和返回之前发生。由于变量已经被放置在寄存器中,因此这将带来性能上的开销。
某些情况下了使用VolatileRead方法来实现内存屏障,代码如下:
public static int VolatileRead(ref int address) { MemoryBarrier(); return address; }
然而,这种方法中的问题是在内存屏障之后读取了int类型的变量。你是否理解了我关于"address"是指向int而不是int本身的指针的备注?请注意,它是ref int,而不是int。
因此,为了确保正确的内存屏障操作,需要使用代理赋值的方式将指针解引用并保存到本地变量中,然后在内存屏障之后返回这个本地变量的值。这样可以确保内存屏障在解引用之后发生,从而正确地同步内存操作。
Volatile和VolatileRead/Write的问题出现的原因是由于需要在解引用指针之后进行内存屏障操作,并且解决方法是使用代理赋值的方式将指针解引用并保存到本地变量中,然后在内存屏障之后返回这个本地变量的值。
Volatile vs VolatileRead/Write?
在.NET 1.1中,不应该使用Thread.VolatileRead/Write()。这是.NET 1.1中的一个设计错误,它使用了完全的内存屏障。在.NET 2.0中进行了修正,但是他们无法再修复这些方法,只能添加了一种新的方法,由System.Threading.Volatile类提供。这是一个JIT(即时编译器)知道的类,它在JIT时用适合特定处理器类型的版本替换这些方法。
在通过参考源代码中的Volatile类的注释中(已编辑以适应),可以看到这一情况:
// 使用具有volatile语义的方法访问内存。与Thread.VolatileRead和Thread.VolatileWrite相比,这些方法的实现更高效。 // //(我们不能更改Thread.VolatileRead/VolatileWrite的实现,因为这样会破坏依赖于其过度强大的排序保证的代码。) // // 这些方法的实际实现通常由VM在JIT时间提供,因为C#不允许我们从byref参数中表达volatile读取/写入。请参见jitinterface.cpp中的getILIntrinsicImplementationForVolatile()。
而且,你会发现很难找到它的使用示例。参考源代码是一个优秀的指南,其中包含了经过精心编写、测试和经受过考验的处理线程的C#代码,但其中使用VolatileRead/Write的次数是零。
坦率地说,.NET内存模型是一团糟,CLR内存模型和C#内存模型之间存在冲突的假设,并且最近为ARM核心添加了新规则。volatile关键字的奇怪语义在不同架构上有不同的含义,这也是一种证据。尽管对于具有弱内存模型的处理器,通常可以假设C#语言规范所说的是准确的。
请注意,Joe Duffy已经放弃了所有希望,并且坚决不建议使用它。通常情况下,假设你可以比语言和框架提供的原语做得更好是非常不明智的。Volatile类的备注部分也强调了这一点:
在正常情况下,C#的lock语句、Visual Basic的SyncLock语句和Monitor类提供了最简单和最不容易出错的同步访问数据的方法,而Lazy类提供了一种简单的方法来编写延迟初始化代码,而不直接使用双重检查锁定。
我不是在开玩笑说“找不到任何示例”...:-)....但确实非常奇怪。在谷歌上找不到一个示例。你能告诉我从哪里可以下载所有的文档吗?
不清楚你所询问的“文档”是什么。参考源代码可以在这里找到:referencesource.microsoft.com/netframework.aspx
volatile关键字是否具有与C#中的Volatile.Read()和Volatile.Write()相同的获取/释放语义?在Mono 4.x中,行为是不同的。
现在在.NET Core 3.1中,可以找到大量的volatile读取和写入操作。
Volatile vs VolatileRead/Write?
当你需要对代码中的fences应用进行更精细的控制时,你可以使用静态的Thread.VolatileRead或Thread.VolatileWrite。
声明一个变量为volatile意味着编译器不会缓存其值,而是始终读取字段的值,并且在执行写操作时,编译器会立即写入分配的值。
Thread.VolatileRead和Thread.VolatileWrite这两个方法允许您在不声明变量为volatile的情况下拥有更精细的控制能力,因为您可以决定何时执行volatile读操作和volatile写操作,而不需要绑定到声明变量为volatile时的无缓存读取和立即写入的限制,因此简单来说,您拥有更多的控制和自由...
VolatileRead()读取内存地址的最新版本,而VolatileWrite()写入地址,使地址对所有线程可用。在变量上一致使用VolatileRead()和VolatileWrite()方法的效果与将其标记为volatile相同。
请参阅通过示例解释差异的博客文章。为什么有int num = address;这一行?它们已经有了明显保存值的address参数。
这是为了进行防御性复制,以避免在我们在方法内部时外部的某些变化。将整数值复制到局部变量中,以避免外部的意外更改。
备注:由于在Visual Basic中不存在volatile关键字,您只能选择一致地使用VolatileRead()和VolatileWrite()静态方法来实现与C#中volatile关键字相同的效果。
你所说的"fine grained control over the way fences"是什么意思?给我展示一件volatile无法做到而volatile read/write可以做到的事情-现在这会教会我。作者也说:"为了更好地控制fences应用在我们的代码中"然后他就停了-没有解释。(此外,我也看到过这篇文章...:-))
"write immediately"默认情况下是吗?为什么会有volatile write的方法?
这是关于更多的控制,使用volatile变量,您无法决定只执行未缓存的读操作或立即写入操作,您被绑定在两者都要执行的情况下,没有选择...请参阅更新的答案,应该能更清楚一些...