Tuesday 9 February 2016

c# - Is a memory fence required here?



Basically I have the following situation:



var tmp1 = new MyObject() { A = i, B = 2 };

// ** write barrier here??
this.Obj = tmp1;


Another thread can do stuff like this:



var tmp = this.Obj;
// ** read barrier here??
use(tmp.A);



Objects like 'Obj' are only written once, then read by multiple threads (multiple times).



I know that Obj is never null in both threads; I also don't care about the synchronization of 'this.Obj'. What I do care about is that once I read the reference tmp = Obj, the contents (e.g. A and B) are also valid.



My question is: Do I need memory barriers (e.g. Thread.MemoryBarrier();) at the above marked positions to ensure that or is this always implicitly OK?






It seems that people dislike this question.




My question originates from the following. I've read up on memory fences and they guarantee: (quote)




The processor executing the current thread cannot reorder instructions in such a way that memory accesses prior to the call to MemoryBarrier execute after memory accesses that follow the call to MemoryBarrier.




If you look at the code, the CPU / compiler might be able to re-write the code:



var tmp1 = new MyObject();

tmp1.A = i;
tmp1.B = 2;
this.Obj = tmp1;


and worse:



var tmp1 = new MyObject();
this.Obj = tmp1;
tmp1.A = i;

tmp1.B = 2;


If another thread picks up the last case, it can read this.Obj from memory, while A and B still have the default value.



Note that it's not just a matter of what the compiler is able to reorder; it's also a matter of what the CPU is allowed to reorder.



In other words: (Thanks @MattBurland )



Is it guaranteed that the object initializer will be run before tmp1 is assigned to this.Obj? Or do I need to use a memory fence to ensure this manually?



Answer



The C# specification guarantees only that reordering cannot affect what the current thread sees. As such, it seems that the JIT is free to reorder operations 2-4 below, as it does not affect the behavior of the producer thread:




  1. Create a new MyObject

  2. Assign i to member A

  3. Assign 2 to member B

  4. Assign the new object to this.Obj




Therefore it seems that a barrier is required between steps 3 and 4. The other option would be to make this.Obj volatile. This would ensure that no other reads or writes are allowed to be moved after the write to this.Obj, also enforcing the desired ordering.


No comments:

Post a Comment

c++ - Does curly brackets matter for empty constructor?

Those brackets declare an empty, inline constructor. In that case, with them, the constructor does exist, it merely does nothing more than t...