volatile是否防止引入读取或写入



在C#中,volatile关键字确保读取和写入分别具有获取和释放语义。然而,它是否说明了介绍的阅读或写作?

例如:

volatile Thing something;
volatile int aNumber;
void Method()
{
// Are these lines...
var local = something;
if (local != null)
local.DoThings();
// ...guaranteed not to be transformed into these by compiler, jitter or processor?
if (something != null)
something.DoThings(); // <-- Second read!

// Are these lines...
if (aNumber == 0)
aNumber = 1;
// ...guaranteed not to be transformed into these by compiler, jitter or processor?
var temp = aNumber;
if (temp == 0)
temp = 1;
aNumber = temp; // <-- An out-of-thin-air write!
}

以下是C#规范1对执行顺序的说明:

C#程序的执行继续进行,以便在关键执行点保留每个执行线程的副作用。副作用定义为读取或写入易失性字段。。。

执行环境可以自由更改C#程序的执行顺序,但受以下限制:

副作用的顺序与易失性读写有关。。。

我当然会考虑引入新的副作用来改变副作用的顺序,但这里没有明确说明。


答案中的链接是列为草稿的C#规范。C#5规范不是草案,但不能在网上获得,只能下载。就我在本节所见,措辞完全相同。

C#规范中的这句话:

副作用的顺序相对于挥发性读写。。。

可以被解释为暗示不允许对volatile变量进行读写引入,但这确实很模糊,这取决于"排序"的含义。如果它指的是现有访问的相对排序,那么引入新的读写不会改变这一点,因此它不会违反规范的这一部分。如果它指的是所有内存访问在程序顺序中的确切位置,那么引入新的访问将违反规范

本文指出,可能会引入对非volatile变量的读取,但没有明确说明是否不允许对volatile变量进行读取。

本问答讨论了如何防止阅读介绍(但没有讨论书面介绍)。

在本文的评论中,两名微软员工(至少在撰写评论时)明确表示,不允许对volatile变量进行读写介绍。

Stephen Toub

"读引子"是一种机制,通过它可以重新排序内存可能会被引入。

Igor Ostrovsky

在C#规范的其他地方,volatile read被定义为"副作用"。因此,重复读取m_paused将是相当于增加了另一个副作用,这是不允许的。

我认为我们可以从这些评论中得出结论,在C#中凭空引入副作用,任何类型的副作用,代码中的任何地方都是不允许的。

CLI标准中的相关引用在第I.12.6.7节中说明了以下内容:

将CIL转换为本机代码的优化编译器不应去除任何挥发性操作,也不得将多个挥发性物质合并操作合并为单个操作。

据我所知,CLI并没有明确谈到引入新的副作用。

我想知道您是否误解了volatile的含义。Volatile可以与可以作为原子操作读取或写入的类型一起使用。

锁没有获取/释放,只有编译时和运行时重新排序的障碍,以提供无锁获取/释放语义(https://preshing.com/20120913/acquire-and-release-semantics/)。在非x86上,这可能需要asm中的屏障指令,但不需要获取锁。

volatile表示一个字段可能会被其他线程修改,这就是为什么读/写需要被视为原子而不是优化的原因。


你的问题有点模棱两可。

1/如果你的意思是,编译器会转换吗:

var local = something;
if (local != null) local.DoThings();

进入:

if (something != null) something.DoThings();

那么答案是否定的。

2/如果你的意思是,"DoThings()"会在同一个对象上被调用两次吗:

var local = something;
if (local != null) local.DoThings();
if (something != null) something.DoThings(); 

那么答案大多是肯定的,除非另一个线程在调用第二个"DoThings()"之前更改了"something"的值。如果是这种情况,那么它可能会给你一个运行时错误——如果在评估"if"条件之后,在调用"DoThings"之前,另一个线程将"something"设置为null,那么你会得到一个运行时间错误。我想这就是为什么你有你的"var local = something;"。

3/如果你的意思是,以下会导致两个读数:

if (something != null) something.DoThings();

则是的、针对该条件的一次读取以及当其调用DoThings()时的第二次读取(假设something不为空)。如果它没有标记为volatile,那么编译器可能会通过一次读取来管理它。

在任何情况下,函数"DoThings()"的实现都需要意识到它可以由多个线程调用,因此需要考虑合并锁和它自己的易失性成员的组合。

相关内容

  • 没有找到相关文章

最新更新