As per the C# specs, is there any guarantee that foo.Bar
would have the same effect of being atomic (i.e. reading foo.Bar
from different threads would never see a partially updated struct when written to by different threads)?
I've always assumed that it does. If indeed, I would like to know if the specification guarantees it.
public class Foo<T> where T : struct
{
private object bar;
public T Bar
{
get { return (T) bar; }
set { bar = value; }
}
}
// var foo = new Foo<Baz>();
EDIT: @vesan This is not the duplicate of Atomic Assignment of Reference Sized Structs. This question asks for the effect of boxing and unboxing whereas the other is about a single reference type in a struct (no boxing / unboxing involved). The only similarities between the two questions are the words struct and atomic (did you actually read the question at all?).
EDIT2: Here's the atomic version based on Raymond Chen's answer:
public class Atomic<T> where T : struct
{
private object m_Value = default(T);
public T Value
{
get { return (T) m_Value; }
set { Thread.VolatileWrite(ref m_Value, value); }
}
}
EDIT3: Revisiting this after 4 years. Turns out that the memory model of CLR2.0+ states that All writes have the effect of volatile write
: https://blogs.msdn.microsoft.com/pedram/2007/12/28/clr-2-0-memory-model/
Thus the answer to this question should have been "It is atomic if hardware does not reorder writes", as opposed to Raymond's answer. The JIT and the compiler cannot reorder writes so the "atomic version" based on Raymond's answer is redundant. On weak memory model architectures, the hardware may reorder writes, so you'll need to appropriate acquire/release semantics.
EDIT4: Again, this issue comes down to CLR vs CLI (ECMA) where the latter defines a very weak memory model while the former implements a strong memory model. There's no guarantee that a runtime will do this though, so the answer still stands. However, since the vast majority of code was and still is written for CLR, I suspect anyone trying to create a new runtime will take the easier path and implement strong memory model at the detriment of performance (just my own opinion).
No, the result is not atomic. While it's true that the update to the reference is atomic, it is not synchronized. The reference can be updated before the data inside the boxed object becomes visible.
Let's take things apart. A boxed type T
is basically something like this:
class BoxedT
{
T t;
public BoxedT(T value) { t = value; }
public static implicit operator T(BoxedT boxed) { return boxed.t; }
}
(Not exactly, but close enough for the purpose of this discussion.)
When you write
bar = value;
this is shorthand for
bar = new BoxedT(value);
Okay, now let's take this assignment apart. There are multiple steps involved.
BoxedT
.BoxedT.t
member with a copy of value
.BoxedT
in bar
.The atomicity of step 3 means that when you read from bar
, you will either get the old value or the new value, and not a blend of the two. But it makes no guarantee about synchronization. In particular, operation 3 may become visible to other processors before operation 2.
Suppose the update of bar
is visible to another processor, but the initialization of the BoxedT.t
is not. When that processor tries to unbox the BoxedT
by reading the Boxed.t
value, it is not guaranteed to read the full value of t
that was written in step 2. It might get only part of the value, and the other part contains default(T)
.
This is basically the same problem with the double checked locking pattern, but worse because you have no lock at all! The solution is to update bar
with release semantics, so that all previous stores are committed to memory before bar
is updated. According to C# 4 language spec, section 10.5.3, this can be done by marking bar
as volatile
. (It also means that all reads from bar
will have acquire semantics, which may or may not be what you want.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With