Is a reference assignment threadsafe?
Asked Answered
S

2

48

I'm building a multi threaded cache in C#, which will hold a list of Car objects:

public static IList<Car> Cars {get; private set;}

I'm wondering if it's safe to change the reference in a thread without locking ?

e.g.

private static void Loop()
{
  while (true)
  {
    Cars = GetFreshListFromServer();
    Thread.Sleep(SomeInterval);
  }
}

Basically it comes down to whether assigning a new reference to Cars is atomic or not I'd guess.

If it's not I'll obviously have to use a private field for my cars, and lock around getting and settings.

Sothena answered 6/3, 2011 at 9:6 Comment(4)
As a side note, a static list is going to be susceptible to mutation issues; don't mutate it outside of locks!Arcature
Good point, I'll probably end up with a custom get and return a copy or readonly version.Sothena
On a side note, have you considered using a ConcurrentDictionary<Car,Car>, to both increase the lookup time O(1), and to manage concurrency? Also, you really should ensure that only one thread is ever going to refresh the list. Otherwise you'll be doing more network calls than you need to.Idiotic
Haven't considered that no. It seems pretty neat, and I might need a dictionary<int, Car> for lookups, so this could come in handy. As for refreshing, I spawn the thread in a static constructor, so there'll only be one.Sothena
A
78

Yes, reference updates are guaranteed to be atomic in the language spec.

5.5 Atomicity of variable references

Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types. In addition, reads and writes of enum types with an underlying type in the previous list are also atomic. Reads and writes of other types, including long, ulong, double, and decimal, as well as user-defined types, are not guaranteed to be atomic.

However inside a tight loop you might get bitten by register caching. Unlikely in this case unless your method-call is inlined (which might happen). Personally I'd add the lock to make it simple and predictable, but volatile can help here too. And note that full thread-safety is more than just atomicity.

In the case of a cache, I'd be looking at Interlocked.CompareExchange, personally - i.e. try to update, but if it fails redo the work from scratch (starting from the new value) and try again.

Arcature answered 6/3, 2011 at 9:8 Comment(6)
+1 Excellent, thanks for the quick reply. You wouldn't happen to have a link to the language spec. If it's available online that is ?Sothena
@Steffan just search for "C# Language Specification", which tends to find the MS versions easily enoough. Or ECMA334 for the 2.0 public version.Arcature
Excellent elaboration, I'll look Interlocked.CompareExchange :-)Sothena
Could someone elaborate on "You might get bitten by register caching"? What would the consequences of this be?Corroborant
@Corroborant simply: thread A doesn't necessarily see changes that thread B makes to a variable. This is particularly noticeable with things like bool shouldRun = true;, while(shouldRun) {...} and a second thread that sets shouldRun = false;Arcature
As of Oct 2018 the latest standard is C# 5 from Dec 2017 available at ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf and the section is: 10.6 Atomicity of variable referencesSvelte
V
0

In @Marc Gravell's answer, as referenced in the C# Language Spec 5.5 it is important to know what is meant by the term "user-defined type". I haven't found a clear definition w.r.t. this usage in the C# Language Spec. In the UML and in common parlance, a Class is an instance of a Type. But in the context of the C# Language Spec, it's meaning is unclear.

The Visual Basic Language Reference section "User Defined Types" (at https://msdn.microsoft.com/en-us/library/cec05s9z.aspx) says

"Previous versions of Visual Basic support the user-defined type (UDT). The current version expands the UDT to a structure."

so it would seem that a User-defined Type is a structure, not a Class.

But ....

According to the "C# Programming Guide" section "Types" (at https://msdn.microsoft.com/en-us/library/ms173104.aspx):

"A typical C# program uses types from the class library as well as user-defined types"

which implies a Class is a user-defined type. Later, it gives an example of "complex user-defined types:"

MyClass myClass;

which means "MyClass" is a user-defined type. And later it says:

"Each type in the CTS is defined as either a value type or a reference type. This includes all custom types in the .NET Framework class library and also your own user-defined types. "

... which would imply that all Classes created by a developer are a "User Defined Type".

And, finally, there is this Stackoverflow item in which the meaning of this term is debated inconclusively: How do I determine if a property is a user-defined type in C#?

Therefore, to be safe, I am forced to consider all Classes, either those that I create or those found in the .Net Framework, all to be User-defined Types, and therefore Not Thread Safe for assignment, because it says in the C# Language Spec section 5.5:

Reads and writes of ... as well as user-defined types, are not guaranteed to be atomic.

It is unfortunate that a colloquial term is used in a precise specification like the C# Language Specification. Because of this ambiguity, in order to be thread-safe, I may be writing less-optimal code than would be possible if it turns out that "User-defined Type" does not include CLR Classes.

Therefore I am asking for further clarification of this stackoverflow answer as it's current basis for the answer leaves this significant ambiguity. As it stands, the answer to the question "Is a reference assignment threadsafe?" seems to be "NO".

Vo answered 24/4, 2015 at 19:56 Comment(1)
Despite the apparent inconsistency in the use of the term "user-defined types", I don't think this calls into question the original answer at all. Excerpting the relevant parts: "Reads and writes of the following data types are atomic: bool, char, ... and reference types. Reads and writes of OTHER TYPES, including ... user-defined types, are not guaranteed to be atomic." In my opinion, "other" in this case clearly excludes anything covered in the initial list (i.e. reference types). Thus, even if "user-defined types" may sometimes be used to describe classes, it certainly doesn't here.Overflow

© 2022 - 2024 — McMap. All rights reserved.