Application and meaning of the volatile keyword

If you read the much-loved msdn, you can find the following wording:

The volatile keyword indicates that the field can be changed multiple threads running simultaneously. Fields declared as volatile, do not pass compiler optimization, which is provides access via a separate thread. This guarantees the presence of the most current value in the field at any time.

And also on a third-party resource there is such as:

According to MSDN, the volatile keyword indicates that a field can be modified by multiple threads running at the same time and therefore The JIT compiler will not perform optimizations with the{[1] field]}

Please correct me, because I'm a little confused, my logic is as follows : I may not directly know that the field value is being modified when the value is actually being modified and from different threads. I tell the compiler that I need to read the field not from the cache, because the cache can get not a valid value, but read directly from the area where it lies, which gives me the right at any time, regardless of whether it has changed or not, to get the really correct value of the field.

Author: Qwertiy, 2017-02-22

2 answers

C volatile it's not as simple as it sounds, because it doesn't do what it does.volatile in C++. Punching caches is only a side effect, and it doesn't work quite as well as you'd expect.

However, it is used most often for the sake of punching caches, and even in MSDN by volatile it is shown exactly on the example of "breaking the cache" when reading a property.

private volatile bool _shouldStop;

// в одном потоке
while (!_shouldStop)
{
    Console.WriteLine("Worker thread: working...");
}

// в другом потоке
_shouldStop = true;

But, at the same time, the same example from MSDN works fine if the word volatile is removed. In what a catch?

What does volatile actually do, and why does it "punch the cache"? And does it break it at all, and are there ways to "break the cache"?

In the C # specification, volatile is mentioned in a couple of places - §3.10 Execution order and §10.5.3 Volatile fields.

3.10 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, a write to a non-volatile variable, a write to an external resource, and the throwing of an exception. The critical execution points at which the order of these side effects must be preserved are references to volatile fields (§10.5.3), lock statements (§8.12), and thread creation and termination.

In fact, the optimizer can not transfer the write or read of a volatile field for lock, transfer it through throw, and through a couple of specific constructs. Not a word about caching. i.e. in the situation

... много кода без critical execution points (локов, работы с volatile и прочим)
volatile read

The optimizer is free to do

volatile read
... много кода без critical execution points (локов, работы с volatile и прочим)

And even lock will not help in this case:

... много кода без critical execution points, сайдэффектов и чтения памяти
lock 
{    
    volatile read
}

Legally turns into

lock 
{    
    volatile read
    ... много кода без critical execution points, сайдэффектов и чтения памяти
}

OK, the second part of the speck

10.5.3 Volatile fields

A read of a volatile field is called a volatile read. A volatile read has "acquire semantics"; that is, it is guaranteed to occur prior to any references to memory that occur after it in the instruction sequence.

Again, not a word about caching. It is claimed that a volatile read will occur no later than it is written in the code (relative to other memory access). Much earlier - no problem!

In fact, volatile prohibits the optimizer from rearranging all accesses to volatile-variables (with each other and with other memory accesses). For non-volatile variables, such permutations are allowed.

I.e. when executing code

// в одном потоке
a = 1;
b = 1
a = 2;
b = 2;

And

// в другом потоке
Console.WriteLine($"{a} {b}");

For non-volatile variables, you can get ... "2 1"

The prohibition of such a permutation is the main purpose of volatile. This is how it is designed, and it is written in this form in the specification.


OK, but it doesn't allow caching, does it? How does he do it? What prevents runtime from turning

while (!_shouldStop)
{
    Console.WriteLine("Worker thread: working...");
}

In

регистр = _shouldStop;
while (регистр)
{
    Console.WriteLine("Worker thread: working...");
}

It is hindered by the section ECMA-335 standard, section I.12.6.7 Volatile reads and writes

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.

The JIT optimizer is simply forbidden from replacing multiple reads (in a loop) with one. Which, in practice, causes it to subtract a value from memory every time that value is mentioned in the code. Which leads to "breaking the cache" - a ban on using the value from the register subtracted in the past referring to the field.

Similarly, with the side effect, the "cache" is broken by the lock:

lock 
{    
    не-volatile read
}

Acquiring a lock shall implicitly perform a volatile read operation

Which prevents the optimizer from rearranging the memory access slightly higher.

 9
Author: PashaPash, 2020-07-31 09:41:45

Well, that's right. Everything that is marked as volatile is read/written from / to where it actually is, without caching, for example, in registers, if this is, of course, possible.

 2
Author: Fynivx, 2017-02-22 09:21:16