Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can boxing/unboxing a struct in C# give the same effect of it being atomic?

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).

like image 863
Zach Saw Avatar asked Sep 23 '15 00:09

Zach Saw


1 Answers

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.

  1. Allocate memory for a BoxedT.
  2. Initialize the BoxedT.t member with a copy of value.
  3. Save a reference to the 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.)

like image 159
Raymond Chen Avatar answered Oct 05 '22 22:10

Raymond Chen