0

If I create a variable on one thread then block using a ManualResetEvent's WaitOne() method until another thread assigns a value to the same variable and signals the EventWaitHandel. When I come to read the variable on the first thread am I guaranteed to always get the value just assigned by the other thread?

(I fear I could not get a value from a CPU cache because of some optimisation as I have not used any memory barriers as far as I know).

e.g.

var str = "multi-threading is hard!";
var mre = new ManualResetEvent(false);
Task.Factory.StartNew(() => 
    {
        str = Console.ReadLine();
        mre.Set();
    ));
mre.WaitOne();
Console.WriteLine(str);
markmnl
  • 11,116
  • 8
  • 73
  • 109

2 Answers2

1

Those instructions will not be reordered, which means that, on the producing thread, the field assignment will always occur before the handle is signaled, and, on the consuming thread, the field will always be read after the handle is signaled.

If any of these two pairs of instructions could be re-ordered (e.g., if the second thread could read the field before the handle was signaled), then you would not see the correct value.

WaitOne() introduces an implicit memory barrier, giving you the acquire-release semantics you need.

Brian Gideon and Hans Passant put together a nice list of several classes in the .NET framework that introduce implicit memory barriers: Memory barrier generators

More info: Acquire and release semantics / Acquire and release fences

Community
  • 1
  • 1
dcastro
  • 66,540
  • 21
  • 145
  • 155
  • I don't see the relevance of re-ordering here, but that a memory barrier is implicit clears it up! – markmnl Feb 21 '14 at 09:08
  • ahh, thanks incidentally how do you know the instructions cannot be reordered - is that because of the memory barrier? – markmnl Feb 21 '14 at 09:12
  • @markmnl I've edited my first paragraph, explaining why re-ordering is relevant. – dcastro Feb 21 '14 at 09:12
  • @markmnl indeed, that's what fences/memory barriers do. Acquire-fences prevent any store/load from moving up the fence, and release-fences prevent any store/load from moving below the fence. Full-fences (e.g., `Thread.MemoryBarrier`) represent full-fences, which prevent any store/load from moving up or below the fence. – dcastro Feb 21 '14 at 09:18
0

Your variable is a captured variable, i.e. the compiler turns this local variable into a field of a compiler-generated class, because you are using it in a lambda expression. Afaik, these compiler generated fields are not marked volatile, so they could be cached.

EDIT: Indeed, the field is not volatile.

You could definitely prevent caching by writing your own class so that the compiler does not have to create one. However, this of course hampers the conciseness of your code.

Georg
  • 5,626
  • 1
  • 23
  • 44
  • yup I am aware it is captured, but if re-wrote the problem with my own class the problem would still be there - I could mark it `volatile` in either. Anyway it seems a memory barrier is implicit so no need. – markmnl Feb 21 '14 at 09:10