volatile 是否会阻止引入的读取或写入?
Does volatile prevent introduced reads or writes?
在 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 对 Execution Order 的说明:
Execution of a C# program proceeds such that the side effects of each executing thread are preserved at critical execution points. A side effect is defined as a read or write of a volatile field ...
The execution environment is free to change the order of execution of a C# program, subject to the following constraints:
...
The ordering of side effects is preserved with respect to volatile reads and writes ...
我当然会考虑引入新的副作用来改变副作用的顺序,但这里没有明确说明。
Link 的答案是列为草案的 C# 6 规范。 C# 5 规范不是草案,但无法在线获取,仅作为 download。就我在本节中看到的相同措辞。
不知你是否误解了volatile
的意思。 Volatile 可与可作为原子操作读取或写入的类型一起使用。
没有 acquire/release 锁 ,只有编译时障碍和 运行 时重新排序以提供无锁 aquire/release语义(https://preshing.com/20120913/acquire-and-release-semantics/)。在非 x86 上,这可能需要 asm 中的屏障指令,但不需要锁定。
volatile
表示一个字段可能被其他线程修改,这就是为什么 read/writes 需要被视为原子而不是优化的原因。
你的问题有点含糊。
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()
”的实现需要意识到它可以被多个线程调用,因此需要考虑合并锁和它自己的易失性成员的组合。
C# 规范中的措辞:
The ordering of side effects is preserved with respect to volatile
reads and writes...
可能会被理解为暗示在volatile
变量上不允许读写引入,但确实有歧义,要看"ordering."的意思 如果是指相对顺序现有访问,然后引入新的读取或写入不会改变这一点,因此它不会违反规范的这一部分。如果它指的是程序顺序中所有内存访问的确切位置,那么引入新的访问将违反规范。
This 文章说可能会引入对非 volatile
变量的读取,但没有明确说明是否不允许对 volatile
变量进行读取。
这篇Q/A讨论如何防止读取引入(但不讨论写入引入)。
在this文章下的评论中,两名微软员工(至少在撰写评论时)明确声明不允许对volatile
变量进行读写介绍。
Stephen Toub
"read introduction" is one mechanism by which a memory reordering
might be introduced.
Igor Ostrovsky
Elsewhere in the C# specification, a volatile read is defined to be a
"side effect". As a result, repeating the read of m_paused would be
equivalent to adding another side effect, which is not allowed.
我认为我们可以从这些评论中得出结论,在 C# 中凭空引入副作用,任何类型的副作用,在代码的任何地方都是不允许的。
CLI 标准的相关引述在第 I.12.6.7 节中说明如下:
An optimizing compiler that converts CIL to native code shall not
remove any volatile operation, nor shall it coalesce multiple volatile
operations into a single operation.
据我所知,CLI 并没有明确提到引入新的副作用。
在 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 对 Execution Order 的说明:
Execution of a C# program proceeds such that the side effects of each executing thread are preserved at critical execution points. A side effect is defined as a read or write of a volatile field ...
The execution environment is free to change the order of execution of a C# program, subject to the following constraints:
...
The ordering of side effects is preserved with respect to volatile reads and writes ...
我当然会考虑引入新的副作用来改变副作用的顺序,但这里没有明确说明。
Link 的答案是列为草案的 C# 6 规范。 C# 5 规范不是草案,但无法在线获取,仅作为 download。就我在本节中看到的相同措辞。
不知你是否误解了volatile
的意思。 Volatile 可与可作为原子操作读取或写入的类型一起使用。
没有 acquire/release 锁 ,只有编译时障碍和 运行 时重新排序以提供无锁 aquire/release语义(https://preshing.com/20120913/acquire-and-release-semantics/)。在非 x86 上,这可能需要 asm 中的屏障指令,但不需要锁定。
volatile
表示一个字段可能被其他线程修改,这就是为什么 read/writes 需要被视为原子而不是优化的原因。
你的问题有点含糊。
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()
”的实现需要意识到它可以被多个线程调用,因此需要考虑合并锁和它自己的易失性成员的组合。
C# 规范中的措辞:
The ordering of side effects is preserved with respect to volatile reads and writes...
可能会被理解为暗示在volatile
变量上不允许读写引入,但确实有歧义,要看"ordering."的意思 如果是指相对顺序现有访问,然后引入新的读取或写入不会改变这一点,因此它不会违反规范的这一部分。如果它指的是程序顺序中所有内存访问的确切位置,那么引入新的访问将违反规范。
This 文章说可能会引入对非 volatile
变量的读取,但没有明确说明是否不允许对 volatile
变量进行读取。
这篇Q/A讨论如何防止读取引入(但不讨论写入引入)。
在this文章下的评论中,两名微软员工(至少在撰写评论时)明确声明不允许对volatile
变量进行读写介绍。
Stephen Toub
"read introduction" is one mechanism by which a memory reordering might be introduced.
Igor Ostrovsky
Elsewhere in the C# specification, a volatile read is defined to be a "side effect". As a result, repeating the read of m_paused would be equivalent to adding another side effect, which is not allowed.
我认为我们可以从这些评论中得出结论,在 C# 中凭空引入副作用,任何类型的副作用,在代码的任何地方都是不允许的。
CLI 标准的相关引述在第 I.12.6.7 节中说明如下:
An optimizing compiler that converts CIL to native code shall not remove any volatile operation, nor shall it coalesce multiple volatile operations into a single operation.
据我所知,CLI 并没有明确提到引入新的副作用。