3

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?

atlaste
  • 30,418
  • 3
  • 57
  • 87
  • A `var` declaration is by definition local and cannot be involved in any multithreading. – H H Jul 24 '15 at 13:17
  • 1
    @HenkHolterman `this` is not. I'm mostly interested in when you should use these concepts (I've already read a ton of information on the internet; this is how I understood it should be used, but I still find it all confusing). As for the C++ remark - I'm interested in the concepts, which should translate to any language... From what I understand, the concepts are 'read', 'write' and 'full' fences, where `Thread.MemoryBarrier` is a full fence. – atlaste Jul 24 '15 at 13:18
  • 1
    If it's only written *once*, then there isn't really an issue here. Unless your concern is with that `this.Obj` is mutable and a reading thread might change `A` and / or `B`. But you said you don't care about synchronization. – Matt Burland Jul 24 '15 at 13:25
  • @MattBurland Well from what I understand, the compiler can reorder the write instructions without a fence, which would mean: first writing the 'new object' to memory, ** then writing 'A' and 'B'. At the '**' another thread could therefore pick it up, reading A=0 or something like that (e.g. the default of A). – atlaste Jul 24 '15 at 13:27
  • 1
    Are you saying that you want to protect the *contents* of `MyObject`? You should be looking at ways to do that from *inside* the object then, eg by making it read-only or immutable – Panagiotis Kanavos Jul 24 '15 at 13:29
  • @PanagiotisKanavos While I agree that using `readonly` is a good idea here, I want to ensure that the contents are written to memory before the pointer is stored. I'm not sure if I can make this assumption without a fence. – atlaste Jul 24 '15 at 13:31
  • Ok, so the question is: *is it guaranteed that the object initializer will be run before `tmp1` is assigned to `this.Obj`*. Is that about right? I would hope so, but I'm not actually positive about that. – Matt Burland Jul 24 '15 at 13:35
  • @MattBurland Yes. Sorry for the confusion; I'll update the question. – atlaste Jul 24 '15 at 13:37
  • 1
    Note that you don't need a full barrier... an half fence is enough. If `this.Obj` is a field, then you can use `Volatile.Write(this.Obj, tmp1);` If not, you can have a `static volatile int _barrier;` and then do: `_barrier = 0; this.Obj = tmp1; ` – xanatos Jul 24 '15 at 14:32
  • In any practical situation, when you share `this.Obj`between threads, access would be guarded with `lock(){}`. The barriers would come free. – H H Jul 24 '15 at 15:28
  • @xanatos Ah thanks a lot, I've been looking for that! I didn't even know that C# has support for read/write barriers (I assume `Volatile.Write` and `Volatile.Read` reflect an atomic write/read with a write/read barrier). – atlaste Jul 24 '15 at 15:30
  • @HenkHolterman False. Lock *implies* a read and a write barrier as part of the underlying *Monitor*. In applications where performance on this level doesn't matter (e.g.: practically all applications - I assume this is what you meant) they're regularly used because the combination makes writing software much easier, but these barriers definitely don't come 'for free'. – atlaste Jul 24 '15 at 15:51
  • @atlaste You assume wrongly. `Volatile.Read`/`Volatile.Write` only give half-barriers equivalent to the ones given by the `volatile` keyword. Atomicity is guaranteed implicitly as in normal reading/writing, so it is excluded for `long`/`ulong`/`double` values at 32 bits, and included for everything else. – xanatos Jul 24 '15 at 16:03
  • @xanatos Ah right, thanks for correcting my 64-bit mistake. I already took the implicit atomicity into account, just incorrectly :-) – atlaste Jul 24 '15 at 16:11

1 Answers1

1

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.

Dark Falcon
  • 43,592
  • 5
  • 83
  • 98
  • Thanks, this is the information I've been looking for. Just wondering, you specifically note 'Create'; would it make a difference if I used a constructor instead of an initializer? – atlaste Jul 24 '15 at 13:44
  • I'm not 100% sure, but I suspect in theory they are equivalent. In practice, I suspect a constructor may unexpectedly resolve the ordering issue merely because it may not be inlined. If it were inlined, the instruction sequence is the same and thus suffers from the same potential for reordering. – Dark Falcon Jul 24 '15 at 13:47
  • OK, so just to be sure. That implies that the read barrier is never needed in such a case, because that cannot be reordered in such a way that it'll give problems. Either the write barrier between 3 and 4 or the volatile (haven't thought about that) will suffice. Correct? – atlaste Jul 24 '15 at 13:56
  • That is correct. Note that if you only use the barrier, your reader may cache the read locally. If you really care about the object being relatively recent, use a lock or use `volatile`. In any case, both the barrier and `volatile` will ensure that the reader never sees an incomplete object. – Dark Falcon Jul 24 '15 at 14:01