4

This question keeps haunting me: in a multithreaded/multiprocessor environment - is it necessary to use explicit locks to synchronize access to shared variables?

Here is a scenario: I have a global variable pointing to a shared object. When an instance of the object is created, the reference to it is placed in the variable and becomes accessible to other threads/processors.

The object itself is immutable - once created it never changes, because of this multiple threads can access it without any additional synchronization.

Once in a while I need to update the object. I do it be creating a new instance of the object on the side and then place a reference to the new object in the global variable.

So here is the question: Can I consider replacing the reference to be an atomic operation. In other words does the variable always have a valid reference to my object - either the old one or the new one?

mfeingold
  • 7,094
  • 4
  • 37
  • 43

8 Answers8

12

is it necessary to use explicit locks to synchronize access to shared variables?

Necessary? No. A good idea? Yes. Low-lock techniques are very hard to get right and rarely justified. Remember, a lock is slow when contested; if your locks are being contested, fix the problem that is causing them to be contested, rather than going to a low-lock solution.

The object itself is immutable - once created it never changes, because of this multiple threads can access it without any additional synchronization.

Awesome.

Once in a while I need to update the object. I do it be creating a new instance of the object on the side and then place a reference to the new object in the global variable.

So here is the question: Can I consider replacing the reference to be an atomic operation. In other words does the variable always have a valid reference to my object - either the old one or the new one?

Yes. The C# specification guarantees that operations on references are atomic. However atomicity is just one small part of thread safety. Atomicity merely guarantees that every time you look at the variable, you get a valid reference out of it. It does not guarantee that every time you look at the variable you get the current reference out of it. Nor does it guarantee that any two threads see the same sequence of changes in the same order. Nor does it guarantee that two atomic updates to two different variables are seen in the order they happened on every thread. Atomicity guarantees you practically nothing, and it might not guarantee you enough to make your program work the way you think it should.

My advice: if you can avoid accessing this variable on multiple threads, do so. If you cannot avoid it, put locks around it. Only if you find that for performance reasons the locks are too slow and you cannot eliminate enough contention should you consider going to dangerous low-lock techniques like making the variable volatile, using interlocked exchanges, and so on.

Community
  • 1
  • 1
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • In my use case the object is a list of available widgets every user of my app can use. Sometimes the list is updated, and no matter what I do there is a possibility that some users are still using the old list while some others will get a new one. In my case this is OK. So, if I understood you correctly in my case I do not need any additional locking. – mfeingold Dec 21 '10 at 17:10
2

Updating a reference is an atomic operation so you do not need to have a locking mechanism around it. Check the C# Language Specification, section 12.5 for this.

Having said this, it may be wise to make your variable volatile (section 17.4.3). This ensures that the compiler does not reorder your statements for optimization. If you want to know all about volatile, read Joseph Albahari's Threading in C# paper.

Ronald Wildenberg
  • 31,634
  • 14
  • 90
  • 133
1

The specification says that variable assignments to reference types are atomic. Therefore your assumption about the variable always holding a valid reference is correct.

However, if "once in a while" is often (for some definition of often), then you should also make the variable volatile. Excellent technical explanation by Eric Lippert here: What is the purpose of 'volatile' keyword in C#.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
1

Assigning an object reference is atomic in .NET. That isn't very likely to help you, some thread might be loading that global variable multiple times while using the object. Working with one version of it, then getting another isn't that likely to come to a good end. You can't call it immutable anymore. Or for that matter loading the global variable and it getting stale while the thread is using it.

If you really need to update that object reference then you need to work out a locking strategy so that all threads are aware of it. Always hard to do with global variables.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
1

Your plan should work for a lot of cases.

There are some big things to watch out for. Never reference the global variable more than once in the current operation. That is, the code should never assume that the global reference hasn’t changed from one instruction to the next.

internal static class EvilSingleton
{
    public static int[] FavoriteNumbers;
}

internal class FavoriteNumberView
{
    private ListBox selectNumberListBox;

    private void LoadNumberListTheWrongWay()
    {
        Debug.Assert(selectNumberListBox != null);

        selectNumberListBox.Clear();

        if (EvilSingleton.FavoriteNumbers == null)
            return;

        // This code is just asking for null reference and array bounds exceptions!!!
        for (int index = 0; index < EvilSingleton.FavoriteNumbers.Length; index++)
            selectNumberListBox.Add(EvilSingleton.FavoriteNumbers[index].ToString());
    }

    private void LoadNumberListTheRightWay()
    {
        // Store the global reference in a local variable, and 
        // only use the local reference from now on.
        int[] favoriteNumbers = EvilSingleton.FavoriteNumbers;

        Debug.Assert(selectNumberListBox != null);

        selectNumberListBox.Clear();

        if (favoriteNumbers == null)
            return;

        // The local reference will never be null, and the array bounds will always be
        // correct. In fact, the optimizer can remove most bounds checking from this 
        // loop, since it knows the array length will not change.
        for (int index = 0; index < favoriteNumbers.Length; index++)
            selectNumberListBox.Add(favoriteNumbers[index].ToString());
     }
}
Jeffrey L Whitledge
  • 58,241
  • 9
  • 71
  • 99
0

No, you can't assume that. You should have some locking mechanism around the object -- in this case a ReaderWriter lock (or ReaderWriterLockSlim) -- any read access, use the reader lock. ANy time you're going to swap it, use the writer lock.

Thanks. You're right, should have checked the spec.

Joe
  • 41,484
  • 20
  • 104
  • 125
  • I think you are wrong. Reference assignment is an atomic operation on all .net platforms. – Jan Dec 21 '10 at 15:23
0

Look at this article: http://iridescence.no/post/The-Atomicity-of-Variable-References.aspx

CLR ensures that the assignment of a reference variable is an atomic operation.

See the answer of Eric Lippert to this question on SO, too.

Community
  • 1
  • 1
Jan
  • 15,802
  • 5
  • 35
  • 59
0

Are you using .NET 4.0? If so, you may want to take a look at the System.Collections.Concurrent Namespace. If you don't need an ordered list, you may be able to use ConcurrentBag.

Rob H
  • 1,840
  • 16
  • 25