tags:

views:

249

answers:

3

In C# 2.0+, is it necessary to lock around a closure that another thread will execute? Specifically, in the example below, is the locking necessary to ensure that the Maint thread flushes its value of x to shared memory and that thread t reads its value of x from shared memory?

I think it is, but if I'm wrong, please reference e.g. an authoritative (e.g., MSDN) article indicating why not. Thanks!

delegate void Foo();
public static void Main()
{
  Foo foo = null;
  object guard = new object();
  int x = 1;
  lock (guard)
  {
    foo = () =>
    {
      int temp;
      lock (guard) temp = x;
      Console.WriteLine(temp);
    };
  }
  Thread t = new Thread(() => foo());
  t.Start();
  t.Join();
}

Edit: Clarified that I want to know for C# 2.0+, which is to say that the .NET 2.0+'s stronger memory model (than ECMA 335 CLI) is in effect.

+1  A: 

Unless more than 1 thread will execute it possibly the same time, no locking is needed.

leppie
Do you have a source that indicates that the CLR memory model will cause the Main thread to push its value of x to main memory and thread t to read its value of x from main memory?
Steve Betten
@Steve: The new thread *has* to read its value from main memory as it hasn't got anywhere else to read it from (because it won't have read it before the thread was started). The memory model for .NET 2.0 ensures that x will be written to main memory.
Jon Skeet
That sounds pretty implementation-specific. A closure is a class at runtime. All the normal threading rules goes.
leppie
If needed, you should probably provide the lock within the closure.
leppie
@leppie: Well, it's specific to the .NET 2.0 memory model. It's hard to talk about what's required without specifying *any* memory model. The CLI spec's model is considerably weaker, which means more work with no cause if only .NET needs to be supported.
Jon Skeet
@Jon: Regarding "The new thread *has* to read its value...", if the new thread had already existed (e.g. if this delegate were going to the GUI thread using BeginInvoke), would I *then* need a lock?
Steve Betten
+4  A: 

Calling any constructor has release semantics in the .NET memory model. (Not in the CLI memory model, but in the .NET one.) So by calling the Thread constructor - and the delegate constructor itself - I believe you're okay. If you set x to 1 after the final constructor I'd be less sure. (EDIT: It's possible that the constructor business is more to do with the new Java memory model than .NET, given the later stuff about writes...)

In fact, I have a suspicion that all writes have release semantics (again, in the implemented .NET memory model) but not all reads have acquire semantics. In other words, the data will always be available to other threads, but it might not be used by those threads. In this case the new thread won't "have" the old value of the variable - there's no way it could have logically read the value before it started, so you're safe on that front.

I can check up on the "all writes" side of things - I suspect there's something in Joe Duffy's blog or book about it.

EDIT: From "Concurrent Programming on Windows" P516:

The major difference in the stronger 2.0+ model is that it prevents stores from being reordered.

I believe that's enough. I suspect that there are very few people on Stack Overflow who are capable of saying for sure - I wouldn't fully trust many people who aren't in the MS CLR or concurrency teams, except maybe Jeff Richter.

Jon Skeet
But for the general case, and to be sure, doing a memory barrier would be a good idea?
MichaelGG
In a more general case, yes. If you do mixed writes and reads in both threads after starting the thread, all bets are off in the normal fashion :)
Jon Skeet
Off to bed now, so I'm afraid any responses to comments won't be read for probably about 12 hours...
Jon Skeet
@Jon: Do you mean "CLI"="ECMA 335" and ".NET"=".NET 2.0 and later"? Separate question: Is it the case that when .NET 2.0 introduced its stronger memory model, the CLI continued to only provide the ECMA 335 memory model? (That would explain why you differentiate between the two.)
Steve Betten
A: 

You need two things:

  1. The assignment should be atomic (not interrupted by another thread that modifies the same data).
  2. The threads need to pick up the current value if it has been changed in another thread.

    1. does not require locking in case of an int (see ECMA 344 C# specification, look for atomic).

    2. is not guaranteed by locking. Use volatile to ensure that (see 17.4.3. in the same document).

Summarising: you do not need the lock, but you may need volatile.

EDIT (See remarks): volatile is not possible on a local variable (x in this case). On a local variable, I don't know of anything that will guarantee that multiple reads of x (in the same thread) will pick up changes that were made in other threads. In the provided code only one read is present, but I guess that this is a simplification.

Renze de Waal
How do you make a local variable volatile?
MichaelGG
You can't declare the variable to be volatile, but *in this case* I don't believe it's necessary.
Jon Skeet
@MichaelGG: Hmm, it is a local variable. I didn't catch that. In that case, volatile is not an option, unless the local variable is changed to a class member. But it does seem silly to do that to be able to use volatile...
Renze de Waal