30

我找不到任何VolatileRead/write (try...) 的例子,但仍然:

我什么时候应该使用volatilevs VolatileRead

AFAIK 的全部目的volatile是创建栅栏,因此:

  • 对于READ操作,在当前操作之后的读/写(在其他线程上)不会在栅栏之前通过。因此 - 我们读取最新值。

问题 #1

那么为什么我需要volatileRead? 似乎volatile已经完成了这项工作。

另外 - 在 C# 中,所有写入都是易失性的(不像在 Java 中说的),无论你是写入一个volatile还是非易失性字段 - 所以我问:为什么我需要 volatileWrite?

问题2

这是实现VolatileRead

[MethodImpl(MethodImplOptions.NoInlining)]
public static int VolatileRead(ref int address)
{
    int num = address;
    MemoryBarrier();
    return num;
}

为什么int num = address;会有这条线?他们已经有了明确持有价值的地址参数。

4

4 回答 4

37

你永远不应该使用 Thread.VolatileRead/Write()。这是 .NET 1.1 中的设计错误,它使用了完整的内存屏障。这在 .NET 2.0 中已得到纠正,但他们无法再修复这些方法,不得不添加由 System.Threading.Volatile 类提供的新方法。这是 jitter 知道的一个类,它用适合特定处理器类型的版本替换jit 时的方法。

可通过参考源获得的 Volatile 类的源代码中的注释讲述了这个故事(已编辑以适应):

// Methods for accessing memory with volatile semantics.  These are preferred over 
// Thread.VolatileRead and Thread.VolatileWrite, as these are implemented more
// efficiently.
//
// (We cannot change the implementations of Thread.VolatileRead/VolatileWrite 
// without breaking code that relies on their overly-strong ordering guarantees.)
//
// The actual implementations of these methods are typically supplied by the VM at 
// JIT-time, because C# does not allow us to express a volatile read/write from/to 
// a byref arg. See getILIntrinsicImplementationForVolatile() in jitinterface.cpp.

是的,你很难找到它的用法示例。参考源是一个很好的指南,其中包含数兆字节的精心编写、测试和战痕累累的处理线程的 C# 代码。它使用 VolatileRead/Write 的次数:

坦率地说,.NET 内存模型与 CLR mm 和 C# mm 做出的相互矛盾的假设一团糟,最近才为 ARM 内核添加了新规则。volatile关键字的怪异语义对不同的架构意味着不同的事物,这就是一些证据。尽管对于内存模型较弱的处理器,您通常可以假设 C# 语言规范所说的内容是准确的。

请注意,Joe Duffy 已经放弃了所有希望,并且完全不鼓励所有使用它。一般来说,假设您可以比语言和框架提供的原语做得更好是非常不明智的。Volatile 类的 Remarks 部分说明了这一点:

一般情况下,C# lock 语句、Visual Basic SyncLock 语句和 Monitor 类提供了同步访问数据的最简单和最不容易出错的方式,而 Lazy 类提供了一种无需直接使用即可编写惰性初始化代码的简单方式双重检查锁定。

于 2013-02-24T14:27:45.520 回答
8

当您需要对栅栏应用于代码的方式进行更细粒度的控制时,您可以使用static Thread.VolatileReadThread.VolatileWrite

声明一个变量 volatile 意味着编译器不会缓存它的值并总是读取字段值,并且当执行写入时,编译器会立即写入分配的值。

Thread.VolatileReadThread.VolatileWrite这两种方法使您能够在不将变量声明为 volatile 的情况下进行更细粒度的控制,因为您可以决定何时执行 volatile 读取操作以及何时执行 volatile 写入,而不必读取 no缓存并在声明变量 volatile 时立即写入,因此简而言之,您拥有更多控制权和更多自由...

VolatileRead()读取最新版本的内存地址,并VolatileWrite()写入该地址,使该地址可用于所有线程。对变量同时使用VolatileRead()VolatileWrite()一致使用与将其标记为易失性具有相同的效果。

看看这篇博客文章,它通过例子解释了差异......

为什么行 int num = address; 有没有 ?他们已经有了明确持有价值的地址参数。

这是一种防御性复制,可以避免外部的东西在我们在方法内部时更改值,整数值被复制到局部变量以避免外部意外更改。

笔记

由于在Visual Basic中不存在 volatile 关键字,因此您只能选择使用一致的VolatileRead()静态VolatileWrite()方法来实现与c#中的 volatile 关键字相同的效果。

于 2013-02-23T10:25:38.820 回答
4

为什么行 int num = address; 有没有 ?他们已经有了明确持有价值的地址参数。

address不是一个int。它是一个int*(所以它确实是一个地址)。该代码正在取消引用指针并将其复制到本地,以便在取消引用之后出现障碍。

于 2013-02-23T10:41:14.523 回答
1

详细说明 aleroot 的答案。

Volatile.Read 和 Volatile.Write 与 Royi Namir 的论点中的 volatile 修饰符相同。但是您可以明智地使用它们。

例如,如果您声明一个带有 volatile 修饰符的字段,那么对该字段的每次访问(无论是读操作还是写操作)都将从非空闲的 CPU 寄存器中读取,这在大多数情况下是不需要的,并且在以下情况下会造成不必要的性能损失字段甚至有很多读取操作。

想想你有私有单例变量被声明为 volatile 并在属性 getter 中返回的场景,一旦它被初始化,你就不需要从 CPU 寄存器中读取它的根,因此你可以使用 Volatile.Read / Write 直到它被创建,一旦创建,所有读取操作都可以像普通字段一样完成,否则会对性能造成很大影响。

而您可以根据需要使用 Volatile.Read 或 Volatile.Write。最好的用途是声明没有 volatile 修饰符的字段,并在需要时使用 Volatile.Read 或 Volatile.Write。

于 2015-07-09T22:44:54.820 回答