在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()
"的实现都需要意识到它可以由多个线程调用,因此需要考虑合并锁和它自己的易失性成员的组合。