5

In Java, a field doesn't need to be volatile if you access it only after joining on the thread that mutated it; the join enforces a happens before relationship.

What about in c#? With the below code, am I guaranteed to see the updated value of _value after calling Join() or do I need to make _value volatile ?

private String _value = "FOO"

public void Foo() 
{

   Thread myThread = new Thread(Bar);
   myThread.Start();
   myThread.Join();
   Console.WriteLine("[Main Thread] _val = "+ _value);

}

public void Bar()
{

   for(int i=0; i<1000; i++)
   {
         Console.WriteLine("Looping");

         if(i==75) 
         {
             _value="BAR";
         }
   }
   Console.WriteLine("DONE Looping");
}

In my code snippet, will "BAR" always be printed?

sstan
  • 35,425
  • 6
  • 48
  • 66
AfterWorkGuinness
  • 1,780
  • 4
  • 28
  • 47
  • You don't need any synchronisation for that code (other than the `.Join()`). Even without it, you wouldn't need to use `volatile` since changing a string reference is an atomic operation, so there's no chance of observing a torn value. – Matthew Watson Jun 07 '16 at 17:44
  • @MatthewWatson, do you mean that because of Join(), the main thread is guaranteed to pickup the new value of _value set by the other thread? – AfterWorkGuinness Jun 07 '16 at 17:46
  • Yes, the other thread will have terminated after `.Join()` returns. – Matthew Watson Jun 07 '16 at 17:46
  • 2
    @MatthewWatson: You're misunderstanding `volatile`. This is about CPU caching. And I'm not sure if it's safe. – SLaks Jun 07 '16 at 17:46
  • 1
    But I suspect that `Join()` involves a memory barrier, so it's _probably_ safe. – SLaks Jun 07 '16 at 17:47
  • Well you shouldn't use `volatile` at all, really. – Matthew Watson Jun 07 '16 at 17:47
  • @MatthewWatson, I am aware that the thread is complete when Join() returns, where I'm not clear is if main memory would have been updated by the time Join() returns, preventing the need for Volatile – AfterWorkGuinness Jun 07 '16 at 17:47
  • 2
    @MatthewWatson: You need to read https://blogs.msdn.microsoft.com/ericlippert/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/ – SLaks Jun 07 '16 at 17:49
  • 1
    `Thread.Join()` should be ok according to http://stackoverflow.com/a/6932271/106159 (@SLaks I was saying `volatile` mistakenly, I should have been talking about the `Volatile` class, not the keyword, hence using `Volatile.Read()` etc - but torn reads are not relevant here, as you say. – Matthew Watson Jun 07 '16 at 17:54
  • 2
    ...except that there is no source for "generally agreed". You can file an issue in the CoreCLR repo to ask for an explicit guarantee. – SLaks Jun 07 '16 at 17:57
  • 1
    In the code you show there's no telling what will be displayed, because the main program never references `_value`. – Jim Mischel Jun 07 '16 at 18:06
  • @JimMischel, fixed typo – AfterWorkGuinness Jun 07 '16 at 18:07
  • 1
    By the way, a good general rule of thumb in C# is that if you think you need to use `volatile`, you're probably doing something wrong. – Jim Mischel Jun 07 '16 at 18:08
  • 1
    @JimMischel I wouldn't say that. I contrast to C volatile has a well-defined and useful meaning in C#. It's a memory access plus a barrier. Also exposed through `class Volatile`. – usr Jun 07 '16 at 21:33
  • @usr I agree: it has a well defined meaning and is useful. In some situations. However, most uses of `volatile` that I see in code are there due to faulty design or, as in this particular case, a misunderstanding of what `volatile` is for. Thus my rule of thumb. – Jim Mischel Jun 07 '16 at 22:33
  • 2
    @usr: You say that like the well-defined meaning yields behaviour which is amenable to comprehension by mortals. My standard test for whether someone actually understands volatile -- and I do not count myself in that group -- is to see if they can solve this puzzle: http://blog.coverity.com/2014/03/26/reordering-optimizations/ – Eric Lippert Jun 07 '16 at 23:41

2 Answers2

3

First off: My general rule of thumb is if I am asking the question "does this need to be volatile?" then I do not understand the memory model well enough to be writing low-lock code.

Just put the thing under a lock and do not attempt low-lock code without an extraordinarily good reason and the advice of an expert.

I am not such an expert. I don't know nearly enough about the C# memory model to write low-lock code with any confidence that it will be correct if run on weak memory model hardware.

To address your actual question:

am I guaranteed to see the updated value of _value after calling Join() or do I need to make _value volatile ?

The answer to your question is in the C# specification, which I quote here for your convenience:

Execution of a C# program proceeds such that the side effects of each executing thread are preserved at critical execution points. A side effect is defined as a read or write of a volatile field, a write to a non-volatile variable, a write to an external resource, and the throwing of an exception. The critical execution points at which the order of these side effects must be preserved are references to volatile fields, lock statements, and thread creation and termination.

You have a write to a non-volatile variable, and the thread is ended by the time the join returns, so the side effect of the write must be preserved at the point of the join.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Nice, so this is specified after all. Although many mysteries remain with other common primitives (not this question). This list from the spec is a drop on a hot stone. – usr Jun 08 '16 at 11:33
1

Common thread synchronization actions perform a full memory barrier. Start and Join and ending a thread surely are among them. Without that all kinds of programs would malfunction.

These guarantees often are not documented but are practically evident. Alas, I cannot provide hard evidence except to say that anything else would be crazy.

See this list as evidence that this is not documented well and that the property you are looking for very likely holds.

In my code snippet, will "BAR" always be printed?

Yes. I believe all experts would agree on that. Here's a simpler code sample that makes the same point:

int x = 0;
Thread myThread = new Thread(() => x = 1);
myThread.Start();
myThread.Join();
x = 2;
Console.WriteLine(x); //Prints 2 because of memory barriers on exit and on Join.
Community
  • 1
  • 1
usr
  • 168,620
  • 35
  • 240
  • 369
  • It shows in what order the strings are output. The console is synchronized. Therefore, this is a good test of global ordering. A global could be used instead if you like that more. @sstan – usr Jun 07 '16 at 21:32
  • Assume there was a barrier issued by join, how could 2,1 ever be printed? (It can't.) – usr Jun 07 '16 at 21:46
  • Note that this is guaranteed to work because Console.WriteLine is a write to "an external resource". – Eric Lippert Jun 08 '16 at 21:13
  • @EricLippert right. Away with that console write. The code snippet was not demonstrating the point. – usr Jun 08 '16 at 21:19