Volatile fields in C#
Asked Answered
R

5

11

From the specification 10.5.3 Volatile fields:


The type of a volatile field must be one of the following:

  • A reference-type.

  • The type byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr, or System.UIntPtr.

  • An enum-type having an enum base type of byte, sbyte, short, ushort, int, or uint.


First I want to confirm my understanding is correct: I guess the above types can be volatile because they are stored as a 4-bytes unit in memory(for reference types because of its address), which guarantees the read/write operation is atomic. A double/long/etc type can't be volatile because they are not atomic reading/writing since they are more than 4 bytes in memory. Is my understanding correct?

And the second, if the first guess is correct, why a user defined struct with only one int field in it(or something similar, 4 bytes is ok) can't be volatile? Theoretically it's atomic right? Or it's not allowed simply because that all user defined structs(which is possibly more than 4 bytes) are not allowed to volatile by design?

Reviviscence answered 25/2, 2011 at 3:41 Comment(7)
See #4727568 (Read Eric Lippert's comment on his answer)Chinook
@The Scrum Meister: Thanks for the link, that perfectly answers my first question. But what about the second? Why can't a 4-byte user defined struct be volatile in C#?Reviviscence
@Danny Probably because they wanted to make it simple. Remember that Typically, the common language runtime controls the physical layout of the data fields of a class or structure in managed memory. (msdn.microsoft.com/en-us/library/…) so you can't be sure of the layout of a struct unless you use the StructLayoutAttribute.Jamima
@xanatos: I also think they want to make it simple. But not so sure, so I hope someone can provide a canonical proof, in case of that I was asked this question in the coming important interview.Reviviscence
@Danny If you are working for an interview, don't use the word "4-bytes". On a 64 bits program IntPtr is 64 bits (clearly IntPtr's size changes to match the architecture)Jamima
@xanatos: Thanks for your tip. Actually I just don't know how to say it in English, so I'm using a weird "4-bytes", haha.Reviviscence
@Danny A IntPtr is a native (platform-specific) size integer #1148677Jamima
T
1

So, I suppose you propose the following point to be added:

  • A value type consisting only of one field which can be legally marked volatile.

First, fields are usually private, so in external code, nothing should depend on a presence of a certain field. Even though the compiler has no issue accessing private fields, it is not such a good idea to restrict a certain feature based on something the programmer has no proper means to affect or inspect.

Since a field is usually a part of the internal implementation of a type, it can be changed at any time in a referenced assembly, but this could make a piece of C# code that used the type illegal.

This theoretical and practical reason means that the only feasible way would be to introduce a volatile modifier for value types that would ensure that point specified above holds. However, since the only group of types that would benefit from this modifier are value types with a single field, this feature probably wasn't very high on the list.

Tort answered 14/1, 2019 at 15:27 Comment(1)
+1 Very reasonable. It's an old question I asked when I was new to the language. I quite agree with you nowadays.Reviviscence
W
1

Basically, usage of the volatile keyword can sometimes be misleading. Its purpose is to allow that the latest value (or actually, an eventually fresh enough value)1 of the respective member is returned when accessed by any thread.

In fact, this is true to value types only2. Reference type members are represented in memory as the pointers to a location in the heap where the object is actually stored. So, when used on a reference type, volatile ensures you only get the fresh value of the reference (the pointer) to the object, not the object itself.

If you have a volatile List<String> myVolatileList which is modified by multiple threads by having elements added or removed, and if you expect it to be safely accessing the latest modification of the list, you are actually wrong. In fact, you are prone to the same issues as if the volatile keyword was not there -- race conditions and/or having the object instance corrupted -- it does not assist you in this case, neither it provides you with any thread safety.

If, however, the list itself is not modified by the different threads, but rather, each thread would only assign a different instance to the field (meaning the list is behaving like an immutable object), then you are fine. Here is an example:

public class HasVolatileReferenceType
{
    public volatile List<int> MyVolatileMember;
}

The following usage is correct with respect to multi-threading, as each thread would replace the MyVolatileMember pointer. Here, volatile ensures that the other threads will see the latest list instance stored in the MyVolatileMember field.

HasVolatileReferenceTypeexample = new HasVolatileReferenceType();
// instead of modifying `example.MyVolatileMember`
// we are replacing it with a new list. This is OK with volatile.
example.MyVolatileMember = example.MyVolatileMember
     .Where(x => x > 42).ToList();

In contrast, the below code is error prone, because it directly modifies the list. If this code is executed simultaneously with multiple threads, the list may become corrupted, or behave in an inconsistent manner.

example.MyVolatileMember.RemoveAll(x => x <= 42);

Let us return to value types for a while. In .NET all value types are actually reassigned when they are modified, they are safe to be used with the volatile keyword - see the code:

public class HasVolatileValueType
{
    public volatile int MyVolatileMember;
}

// usage
HasVolatileValueType example = new HasVolatileValueType();
example.MyVolatileMember = 42;

1The notion of lates value here is a little misleading, as noted by Eric Lippert in the comments section. In fact latest here means that the .NET runtime will attempt (no guarantees here) to prevent writes to volatile members to happen in-between read operations whenever it deems it is possible. This would contribute to different threads reading a fresh value of the volatile member, as their read operations would probably be ordered after a write operation to the member. But there is more to count on probability here.

2In general, volatile is OK to be used on any immutable object, since modifications always imply reassignment of the field with a different value. The following code is also a correct example of the use of the volatile keyword:

public class HasVolatileImmutableType
{
    public volatile string MyVolatileMember; 
}

// usage
HasVolatileImmutableType example = new HasVolatileImmutableType();
example.MyVolatileMember = "immutable";
// string is reference type, but is *immutable*, 
// so we need to reasign the modification result it in order 
// to work with the new value later
example.MyVolatileMember = example.MyVolatileMember.SubString(2);

I'd recommend you to take a look at this article. It thoroughly explains the usage of the volatile keyword, the way it actually works and the possible consequences to using it.

Wessex answered 12/11, 2012 at 10:58 Comment(6)
This is tremendously unsafe. Another thread can mutate the volatile member between the read (for substring) and the write.Homburg
@Puppy, that is true, but a completely different problem. volatile is not something that causes synchrionization, it makes sure you only read the latest value stored in a volatile member, which is not guaranteed for non-volatile members since they lack of memory synchronization between threads. The problem I am emphasizing on, is that you can still corrupt a member declared as volatile if it being mutated.Wessex
I know I am many years late to critique this answer, but I note that it makes the same misleading claim several times. Volatile does not guarantee that you read the most up-to-date value, because the notion that there is such a thing as a consistent, universally-agreed-to "up to date value" is simply false. Many people believe what you do: that volatile gives you a "freshness" guarantee. Unfortunately, many places in the Microsoft documentation reinforce this false belief system. The C# specification says what volatile really does.Alcoholic
In short: volatile restricts the compiler, the runtime and the CPUs ability to re-order certain reads and writes with respect to each other. Reads can be moved backwards in time and writes can be moved forwards in time; volatile restricts the set of legal moves. That is all that it is guaranteed to do and you should not rely on it doing more. In particular, the C# specification calls out explicitly that there is no guarantee that a consistent ordering of reads and writes can be agreed to by all threads.Alcoholic
Since there is no guarantee that there is a consistent order, there is no such thing as "the latest value", because having a "latest value" implies that everyone agrees what "latest" means.Alcoholic
@EricLippert, indeed this is so. I have corrected my claim as there is indeed no-guarantee for absolute freshness of the data being read. Thanks for pointing this out, this is an important topic to not get people confused over.Wessex
T
1

So, I suppose you propose the following point to be added:

  • A value type consisting only of one field which can be legally marked volatile.

First, fields are usually private, so in external code, nothing should depend on a presence of a certain field. Even though the compiler has no issue accessing private fields, it is not such a good idea to restrict a certain feature based on something the programmer has no proper means to affect or inspect.

Since a field is usually a part of the internal implementation of a type, it can be changed at any time in a referenced assembly, but this could make a piece of C# code that used the type illegal.

This theoretical and practical reason means that the only feasible way would be to introduce a volatile modifier for value types that would ensure that point specified above holds. However, since the only group of types that would benefit from this modifier are value types with a single field, this feature probably wasn't very high on the list.

Tort answered 14/1, 2019 at 15:27 Comment(1)
+1 Very reasonable. It's an old question I asked when I was new to the language. I quite agree with you nowadays.Reviviscence
F
0

I think it is because a struct is a value type, which is not one of the types listed in the specs. It is interesting to note that reference types can be a volatile field. So it can be accomplished with a user-defined class. This may disprove your theory that the above types are volatile because they can be stored in 4 bytes (or maybe not).

Fruitarian answered 25/2, 2011 at 4:16 Comment(2)
Wrong. References are 4 bytes.Grefer
It is the 4-byte reference that is volatile, not the actual type used. The reference itself is not much different than a value type.Wessex
S
0

This is an educated guess at the answer... please don't shoot me down too much if I am wrong!

The documentation for volatile states:

The volatile modifier is usually used for a field that is accessed by multiple threads without using the lock statement to serialize access.

This implies that part of the design intent for volatile fields is to implement lock-free multithreaded access.

A member of a struct can be updated independently of the other members. So in order to write the new struct value where only part of it has been changed, the old value must be read. Writing is therefore not guaranteed to require a single memory operation. This means that in order to update the struct reliably in a multithreaded environment, some kind of locking or other thread synchronization is required. Updating multiple members from several threads without synchronization could soon lead to counter-intuitive, if not technically corrupt, results: to make a struct volatile would be to mark a non-atomic object as atomically updateable.

Additionally, only some structs could be volatile - those of size 4 bytes. The code that determines the size - the struct definition - could be in a completely separate part of the program to that which defines a field as volatile. This could be confusing as there would be unintended consequences of updating the definition of a struct.

So, whereas it would be technically possible to allow some structs to be volatile, the caveats for correct usage would be sufficiently complex that the disadvantages would outweigh the benefits.

My recommendation for a workaround would be to store your 4-byte struct as a 4-byte base type and implement static conversion methods to use each time you want to use the field.

Silverplate answered 2/11, 2012 at 1:6 Comment(0)
L
0

To address the second part of your question, I would support the language designers decision based on two points:

KISS - Keep It Simple Simon - It would make the spec more complex and implementations hard to have this feature. All language features start at minus 100 points, is adding the ability to have a small minority of struts volatile really worth 101 points?

Compatibility - questions of serialization aside - Usually adding a new field to a type [class, struct] is a safe backwards source compatible move. If you adding a field should not break anyones compile. If the behavior of structs changed when adding a field this would break this.

Landwehr answered 26/11, 2012 at 18:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.