问题
In C#, volatile
keyword ensures that reads and writes have acquire and release semantics, respectively. However, does it say anything about introduced reads or writes?
For instance:
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!
}
回答1:
Here's what the C# spec1 has to say about 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 ...
I would certainly consider introducing new side effects to be changing the order of side effects, but it's not explicitly stated like that here.
Link in answer is to the C# 6 spec which is listed as Draft. C# 5 spec isn't a draft but is not available on-line, only as a download. Identical wording, so far as I can see in this section.
回答2:
This wording from the C# spec:
The ordering of side effects is preserved with respect to volatile reads and writes...
may be interpreted as implying that read and write introductions on volatile
variables are not allowed, but it is really ambiguous and it depends on the meaning of "ordering." If it is referring to relative ordering of existing accesses, then introducing new reads or writes does not change that and so it would not violate this part of the spec. If it is referring to the exact position of all memory accesses in program order, then introducing new accesses would violate the spec.
This article says that reads on non-volatile
variables might be introduced but does not say explicitly whether this is not allowed on volatile
variables.
This Q/A discusses how to prevent read introduction (but no discussion on write introduction).
In the comments under this article, two Microsoft employees (at the least at the time the comments were written) explicitly state that read and write introductions on volatile
variables are not allowed.
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.
I think we can conclude from these comments that introducing a side effect out-of-thin-air in C#, any kind of side effect, anywhere in the code is not allowed.
A related quote from the CLI standard states the following in Section 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.
As far as I know, the CLI does not explicitly talk about introducing new side effects.
回答3:
I wonder whether you have misunderstood what volatile
means. Volatile can be used with types that can be read or written as an atomic action.
There is no acquire/release of a lock, only a barrier to compile-time and run-time reordering to provide lockless aquire/release semantics (https://preshing.com/20120913/acquire-and-release-semantics/). On non-x86 this may require barrier instructions in the asm, but not taking a lock.
volatile
indicates that a field may be modified by other threads, which is why read/writes need to be treated as atomic and not optimised.
Your question is a little ambiguous.
1/ If you mean, will the compiler transform:
var local = something;
if (local != null) local.DoThings();
into:
if (something != null) something.DoThings();
then the answer is no.
2/ If you mean, will "DoThings()
" be called twice on the same object:
var local = something;
if (local != null) local.DoThings();
if (something != null) something.DoThings();
then the answer is mostly yes, unless another thread has changed the value of "something
" before the second "DoThings()
" is invoked. If this is the case then it could give you a run time error - if after the "if
" condition is evaluated and before "DoThings
" is called, another thread sets "something
" to null
then you will get a runtime error. I assume this is why you have your "var local = something;
".
3/ If you mean will the following cause two reads:
if (something != null) something.DoThings();
then yes, one read for the condition and a second read when it invokes DoThings()
(assuming that something
is not null). Were it not marked volatile
then the compiler might manage that with a single read.
In any event the implementation of the function "DoThings()
" needs to be aware that it could be called by multiple threads, so would need to consider incorporating a combination of locks and its own volatile members.
来源:https://stackoverflow.com/questions/53079587/does-volatile-prevent-introduced-reads-or-writes