C# bool is atomic, why is volatile valid
Asked Answered
F

2

14

In C#, we know that a bool is atomic - then why is it valid to mark it as volatile? what is the difference and what is a good (or even practical) use-case for one versus the other?

bool _isPending;

Versus

volatile bool _isPending; // Is this realistic, or insanity?

I have done some reading here and here, and I'm trying to ensure that I fully understand the inner workings of the two. I want to understand when it is appropriate to use one vs the other, or if just bool is enough.

Fist answered 3/7, 2016 at 1:47 Comment(9)
I'm pretty sure for every volatile bool use case there exists another more optimal solution. SO in theory bool will always be enough.Mesh
In theory, there is no difference between theory and practice. But in practice it is.Lawrenson
@Mesh that's really not the case. A volatile bool is the cheapest and fastest way to safely publish data (the simplest example being an exit flag).Emirate
@vmatm That's only if if there's a bug somewhere in the environment. In this case there's a gigantic difference in theory and practice between the two versionsEmirate
@Emirate Could you please be more specific? May be some articles or blog posts or MDSN?Lawrenson
@Emirate you should post an answerFist
@Lawrenson C#'s memory model is generally rather underspecified, but volatile gives you acquire/release semantics which in turn provide reordering and visibility guarantees (simple example of a valid optimisation without volatile). Java's and .NET's memory model are pretty similar all considered though, so you can certainly learn a lot from reading this and afterwards this.Emirate
I know all of this, just can't get your point about the bug in the environment.Lawrenson
@vtatm If theory and practice disagree the implementation is not in sync with the specification. This is by definition a bug in the implementation.Emirate
S
21

In C#, we know that a bool is atomic - then why is it valid to mark it as volatile? what is the difference and what is a good (or even practical) use-case for one versus the other?

The supposition of your question is that you believe that volatile makes an access atomic. But volatility and atomicity are completely different things, so stop conflating them.

Volatility is the property that the compiler and runtime are restricted from making certain optimizations involving moving reads and writes of variables forwards and backwards in time with respect to each other, and more generally, with respect to other important events such as starting and stopping threads, running constructors, and so on. Consult the C# specification for a detailed list of how operations may or may not be re-ordered with respect to visible side effects.

Atomicity is the property that a particular operation can only be observed as not started or totally completed, and never "halfway done".

As you can see from the definitions, those two things have nothing whatsoever to do with each other.

In C#, all accesses to references, bools, and integer types of size 4 and smaller are guaranteed to be atomic.

Now, in C# there is some slight non-orthogonality between atomicity and volatility, in that only fields of atomic types may be marked as volatile. You may not make a volatile double, for example. It would be really weird and dangerous to say "we're going to restrict how reads and writes may be optimized but still allow tearing". Since volatility does not cause atomicity, you don't want to put users in a position of thinking that an operation is atomic just because it is also volatile.

You should read my series of articles that explains in far more detail what the differences between these things are, and what volatile actually does, and why you do not understand nearly enough to be using it safely.

https://ericlippert.com/2011/05/26/atomicity-volatility-and-immutability-are-different-part-one/

https://ericlippert.com/2011/05/31/atomicity-volatility-and-immutability-are-different-part-two/

https://ericlippert.com/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/

https://web.archive.org/web/20160323025740/http://blog.coverity.com/2014/03/12/can-skip-lock-reading-integer/

If you think you understand volatility after reading all that, I invite you to try to solve the puzzle I pose here:

https://web.archive.org/web/20160729162225/http://blog.coverity.com/2014/03/26/reordering-optimizations/

Schach answered 4/7, 2016 at 14:39 Comment(2)
Thank you for the detailed answer. I have read through the articles you shared and they were very helpful.Fist
@DavidPine: You are very welcome; glad to help! This is a very tricky subject.Schach
R
6

If there are updates to variables in the preceding or subsequent code and the order in which the updates occurs is critical, then marking the field as volatile will ensure that an update to that field will happen after any previous updates and before any subsequent updates.

In other words, if _isPending is volatile, then the compiler will not cause these instructions to execute in a different order:

_someVariable = 10;
_isPending = true;
_someOtherVariable = 5;

Whether multi-threaded or not, if we've written code that breaks depending on whether these updates in adjacent lines occur in the specified order then something is wrong. We should ask why that sequence matters. (If there is a scenario where that matters, imagine trying to explain it in a comment so that no one makes a breaking change to the code.)

To nearly anyone reading the code above it would appear that the order of those operations doesn't matter at all. If they do matter that means that someone else who reads our code can't possibly understand what's going on. They could do some refactoring, reorder those lines of code, and break everything without knowing it. It might even work when they test it and then fail unpredictably and inconsistently when it's deployed.

I agree with Eric Lippert's comment in the answer you linked:

Frankly, I discourage you from ever making a volatile field. Volatile fields are a sign that you are doing something downright crazy: you're attempting to read and write the same value on two different threads without putting a lock in place.


I suppose I failed to directly answer the direction. volatile is valid for a type (including bool) because it's possible to perform an atomic operation on that type. volatile protects from compiler optimizations. According to the documentation for volatile,

This ensures that the most up-to-date value is present in the field at all times.

But if the field can't be represented in 32 bits or less then preventing compiler optimizations can't guarantee that anyway.

Repartee answered 3/7, 2016 at 2:21 Comment(4)
Volatile is about much much more than just forbidding the reordering of instructions! Simplified it gives ordering and visibility guarantees. Just because you execute the above instructions in the right order would normally not guarantee the "expected" result (It might work on x86 but that's the exception to the rule).Emirate
True, which only reinforces rhe point. If code depends on the behavior of volatile then it's inherently more susceptible to failure. If they just used a lock then it wouldn't matter which processor it ran on.Repartee
If you're using volatile correctly you get the same behavior on every platform. If they wrote incorrect code with locking it would fail just as non deterministically. It is true though that writing correct code with low level primitives is exceedingly hard and best avoided.Emirate
@ScottHannen Thank you for your answer.Fist

© 2022 - 2024 — McMap. All rights reserved.