views:

396

answers:

4

New to this website, so let me know if I'm not posting in an accepted manner.

I've frequently coded something along the lines of the sample below(with stuff like Dispose ommited for clarity. ). My question is, are the volatiles needed as shown? Or does the ManualResetEvent.Set have an implicit memory barrier as I've read Thread.Start does? Or would an explicit MemoryBarrier call be better than the volatiles? Or is it completely wrong? Also, the fact that the "implicit memory barrier behavior" in some operations is not documented as far as I've seen is quite frutrating, is there a list of these operations somewhere?

Thanks, Tom

:

class OneUseBackgroundOp
{

   // background args
   private string _x;
   private object _y;
   private long _z;

   // background results
   private volatile DateTime _a
   private volatile double _b;
   private volatile object _c;

   // thread control
   private Thread _task;
   private ManualResetEvent _completedSignal;
   private volatile bool _completed;

   public bool DoSomething(string x, object y, long z, int initialWaitMs)
   {
      bool doneWithinWait;

      _x = x;
      _y = y;
      _z = z;

      _completedSignal = new ManualResetEvent(false);

      _task = new Thread(new ThreadStart(Task));
      _task.IsBackground = true;
      _task.Start()

      doneWithinWait = _completedSignal.WaitOne(initialWaitMs);

      return doneWithinWait;

   }

   public bool Completed
   {
      get
      {
         return _completed;
      }
   }

   /* public getters for the result fields go here, with an exception
      thrown if _completed is not true; */

   private void Task()
   {
      // args x, y, and z are written once, before the Thread.Start
      //    implicit memory barrier so they may be accessed freely.

      // possibly long-running work goes here

      // with the work completed, assign the result fields _a, _b, _c here

      _completed = true;
      _completedSignal.Set();

   }

}
A: 

Based on what you've shown, I would say that, no, the volatiles are not required in that code.

The ManualResetEvent itself doesn't have an implicit memory barrier. However, the fact that the main thread is waiting for the signal means that it can't modify any variables. At least, it can't modify any variables while it's waiting. So I guess you could say that waiting on a synchronization object is an implicit memory barrier.

Note, however, that other threads, if they exist and have access to those variables, could modify them.

From your question, it seems that you're missing the point of what volatile does. All volatile does is tell the compiler that the variable might be modified by other threads asynchronously, so it shouldn't optimize code that accesses the variable. volatile does not in any way synchronize access to the variable.

Jim Mischel
No, it doesn't synchronize, but it does change memory visibility rules, which is what this question is really about. _volatile_ does more than just keep the compiler from optimizing code in .NET (unlike in C++).
wekempf
According to the C# language specification, documentation for the MSIL Volatile opcode, and the MSDN C++ documentation, volatile in .NET and volatile in native C++ do exactly the same thing: force 'acquire' semantics on read and 'release' semantics on write.
Jim Mischel
The current C++ standard (not the upcoming C++0x) makes no guarantees about memory visibility anywhere, much less with regard to volatile. If MSDN mentions this, it's either talking about the MS implementation or the upcoming standard.
wekempf
So are you saying that volatile does or does not change memory visibility? Your second comment seems to contradict your first.
Jim Mischel
The first comment was about .NET, where volatile does change visibility. The second is about C++, where the current standard makes no guarantees and one must assume it doesn't change visibility.
wekempf
+2  A: 
Matt Davis
Like I replied to Jim Mischel's comment, what's being discussed here is memory visibility, where _volatile_ is very relevant.
wekempf
In the link provided, my sample fits the "Footnote: What volatile is actually good for." in that you've got 1 writer, and subsequently, 1 reader, with the EventWaitHandle protecting against "early" access of the result. Volatile was just to avoid the memory caching issues (stale values)
tcarvin
No, your sample doesn't seem to fit, as my answer indicated. The Wait operation ensures you have no memory caching issues. The footnote was suggesting a faster solution that wouldn't require a "lock" (the Wait is a "lock" in this regard).
wekempf
Perhaps I'm misunderstanding, but how does the Wait operation ensure no memory caching related to the volatile fields? Are you talking about the ManualResetEvent itself?
Matt Davis
From "CLR via C#", all thread synchronization locks (Wait in this case) ensure memory visibility.
wekempf
An explicit lock has 2 effects: 1. It prevents multiple threads from accessing the same section of code at the same time 2. It creates a memory barrier (as in Thread.MemoryBarrier()). In my sample I thought the EventWaitHandle does the former, and volatile does the latter.
tcarvin
It's great if "The Wait operation ensures you have no memory caching issues" is correct, I'm just looking for documentation on *why*
tcarvin
A Wait on an EventWaitHandle is a "lock" and performs both operations.
wekempf
Why, is because it's designed to ;). Wouldn't work well as a synchronization concept if it didn't.
wekempf
Followup: Given thread L calls DoSomething() (and is safe from needing volatiles because if the WaitOne), and thread M is the background thread running Task(), do the volatiles permit some thread N to call without error:if (oneUseBackOp.Completed) MessageBox.Show(oneUseBackPp.C.ToString)
tcarvin
Basically, because the result vars and completed flag are volatile, and are write-once read-many, I should not have to perform a lock to ensure that if Completed returns true then the result variables are visible in memory to all threads?
tcarvin
That's correct, you shouldn't need a lock (or volatile) here, so long as the point is to initialize shared state that's only ever read and not written after initialization.
wekempf
But that's not what's going on, is it? The result variables are written every time the background thread is run. You need something that synchronizes access to the data.
Matt Davis
My understanding is that the background thread is run once, solely to initialize the data. If that understanding is incorrect, then certainly you do need to synchronize.
wekempf
The sample class was called "OneUseBackgroundOp", so yes it was one use in this case!
tcarvin
@Matt Davis, I clarified my usage in an "edit" to answer your questions, thanks!
tcarvin
+2  A: 

Note that this is off the cuff, without studying your code closely. I don't think Set performs a memory barrier, but I don't see how that's relevant in your code? Seems like more important would be if Wait performs one, which it does. So unless I missed something in the 10 seconds I devoted to looking at your code, I don't believe you need the volatiles.

Edit: Comments are too restrictive. I'm now referring to Matt's edit.

Matt did a good job with his evaluation, but he's missing a detail. First, let's provide some definitions of things thrown around, but not clarified here.

A volatile read reads a value and then invalidates the CPU cache. A volatile write flushes the cache, and then writes the value. A memory barrier flushes the cache and then invalidates it.

The .NET memory model ensures that all writes are volatile. Reads, by default, are not, unless an explicit VolatileRead is made, or the volatile keyword is specified on the field. Further, interlocked methods force cache coherency, and all of the synchronization concepts (Monitor, ReaderWriterLock, Mutex, Semaphore, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally, and thus ensure cache coherency.

Again, all of this is from Jeffrey Richter's book, "CLR via C#".

I said, initially, that I didn't think Set performed a memory barrier. However, upon further reflection about what Mr. Richter said, Set would be performing an interlocked operation, and would thus also ensure cache coherency.

I stand by my original assertion that volatile is not needed here.

Edit 2: It looks as if you're building a "future". I'd suggest you look into PFX, rather than rolling your own.

wekempf
Please explain how volatile is relevant to memory visibility.
Jim Mischel
In the CLR, volatile provides memory visibility semantics, according to Jeffrey Richter's "CLR via C#". Reads of volatile data are unordered, while writes use release semantics.
wekempf
Richter's book says (on page 628) that on the IA64, "all writes are always performed with release semantics, but reads are allowed to be unordered; programmers must still call Thread's VolatileRead or apply the C# volatile keyword to a field so that it is read with acquire semantics."
Jim Mischel
On pages 626 and 627, he describes the operation of volatile, a description that agrees with my comment that volatile works the same in C# as it does in C++.
Jim Mischel
No, it doesn't. On page 627 he explicitly states that access to a volatile field is done with volatile reads and writes, i.e. using memory barriers. C++ (current, not C++0x) makes no such guarantee. In fact, it makes no mention of memory visibility at all.
wekempf
@wekempf: I am humbly skeptical about your answer. I searched the internet for any reference to EventWaitHandle and memory barriers and couldn't find one. Can you please provide a link where this is explained?
Matt Davis
My reference is the "CLR via C#" book by Jeffrey Richter, and as stated throughout here it can be found on pages 626-628.
wekempf
@Matt Davis, further, this is the behavior you'd want from any synchronization object. No other behavior would make sense.
wekempf
ManualResetEvent is a thread synchronization object, not a data access synchronization object. Please see the edit on my original post. I'm not trying to drive a wedge here. If I'm wrong, I'll be more than happy to revise my remarks to make sure what I've said is accurate.
Matt Davis
You make a valid point about the distinction... which is actually why the Event synchronization concept should really be avoided (http://wekempf.spaces.live.com/blog/cns!D18C3EC06EA971CF!672.entry). However, it still only makes sense to perform a MB here, and according to Richter, it does.
wekempf
@wekempf: I've made one final revision to my post. Please review for concurrence.
Matt Davis
Mostly concur. What he's doing is a poor man's pthread_once. Access to the data later is thread safe and doesn't need a lock. However, there's better ways to do one time initialization (static constructor or even Double-checked lock, which does require volatile).
wekempf
Please see my editted answer, its intended to simplying operations that usually succeed quickly (within the initialWait) but can occasionally run longer.
tcarvin
@wekempf, thanks for the link on EventWaitHandles. Good stuff.
tcarvin
+1  A: 

First, I'm not sure if I should "Answer my own question" or use a comment for this, but here goes:

My understanding is that volatile prevents code/memory optimizations from moving the accesses to my result variables (and the completed boolean) such that the the thread that reads the result will see upt-to-date data.

You wouldn't want the _completed boolean made visible to all threads after the Set() due to compiler or emmpry optimaztions/reordering. Likewise, you wouldn't want the writes to the results _a, _b, _c being seen after the Set().

EDIT: Further explaination/clarification on the question, in regards to items mentioned by Matt Davis:

Finally, i should point out that whenever a thread calls an interlocked method, the CPU forces cache coherency. So if you are manipulating variables via interlocked methods, you do not have to worry about all of this memory model stuff. Furthermore, all thread synchronization locks (Monitor, ReaderWriterLock, Mutex, Semaphone, AutoResetEvent, ManualResetEvent, etc.) call interlocked methods internally.

So it would seem that, as wekempf pointed out, that the result variables do not require the volatile keyword in the example as shown since the ManualResetEvent ensures cache coherency.

So you are both in agreement that such an operation takes care of caching between processors or in registers etc.

But does it prevent reording to guarantee such that BOTH the results are assigned before the completed flag, and that the completed flag is assigned true before the ManualResetEvent is Set?

First, my initial assumption was that the background thread would potentially run multiple times. I obviously overlooked the name of the class (OneUseBackgroundOp)! Given that it is only run once, it is not clear to me why the DoSomething() function calls WaitOne() in the manner that it does. What is the point of waiting initialWaitMs milliseconds if the background thread may or may not be done at the time DoSomething() returns? Why not just kickoff the background thread and use a lock to synchronize access to the results variables OR simply execute the contents of the Task() function as part of the thread that calls DoSomething()? Is there a reason not to do this?

The concept of the sample is to execute a possibly long-runnig task. If the task can be completed within an exceptable amount of time, then the calling thread will get access to the result and continue with normal processing. But sometime a task can take quite a long time and the claiing thread cannot be blocked for that period and can take reasonable steps to deal with that. That can include checking back later on the operation using the Completed property.

A concrete example: A DNS resolve is often very quick (subsecond) and worth waiting for even from a GUI, but sometimes it can take many many seconds. So by using a utility class like the sample, one could gets a result easily from the point-of-view of the caller 95% of the time and not lock up the GUI the other 5%. One could use a Background worker, but that can be overkill for an operation that the vast majority of the time doesn't need all that plumbing.

Second, it seems to me that not using some kind of locking mechanism on the results variables is still a bad approach. True, it is not needed in the code as shown.

The result (and completed flag) data is meant to be write-once, read-many. If I added a lock to assign the results and flag, I'd also have to lock in my result getters, and I never liked seeing getters lock just to return a data point. From my reading, such fine-grained locking is not appropriate. If an operation has 5 or 6 results, the caller has to take and release the lock 5 or 6 times needlessly.

But at some point down the road, another thread may come along and try to access the data. It would be better in my mind to prepare for this possibility now rather than try to track down mysterious behavior anomalies later.

Because I have a volatile completed flag that is guarenteed to be set before the volatile results are, and the only access to the results is through the getters, and as mentioned in the smaple, an exception is thrown if the getter is called and the operation is not yet complete, I'd expect that the Completed and result getters CAN be invoked by a thread other than the one that called DoSomething(). That's my hope anyway. I believe this to be true with the volatiles anyway.

tcarvin
I had hoped for a confirm or deny on my affirmation speculation that by using volatiles in the sample, it would allow a third thread to also fetch the result values without being concerned about uninitailzed data getting returned.
tcarvin