.NET Threading :: Part 1 - Basic Threading

Posted Tuesday, September 09, 2008 1:18 PM by Nathan Zaugg

Threading Recently I have been doing a lot with threading.  This is a concept that used to be very difficult for me and now is only just difficult! smile_regular  Threading is becoming increasingly important as modern processors are not getting faster, they are getting more cores.  In order for a application to utilize any of the power of modern CPU's it must use threading! So I thought I would take a second and go through all of the classes in the System.Threading namespace.  We'll start with the simple stuff and move on to more advanced stuff!

The most familiar and basic structure for locking in .net is the lock statement shown below:

lock (this) {
    ObjectCount = value;
}

The lock is a simple synchronization structure which will only allow a single thread into the "critical" locked section at a time.  Many people don't realize this but the following code does the exact same thing:

Monitor.Enter(this);
    ObjectCount = value;
Monitor.Exit(this);

In fact, the Monitor object can do a lot more than enter a critical section; though I've not found a lot of use for the other functions.  But some operations of note are Monitor.TryEnter which allows you to specify a time span to wait for the lock.  Monitor.Pulse and Monitor.PulseAll notify the next waiting thread or all waiting threads respectively that a lock has been released.  This is not necessarily required but may result in the next thread being able to enter the critical section a bit quicker.  Monitor.Wait will release the lock to let another thread enter and then re-acquire the lock. 

Another note-worthy threading trick is the keyword volatile. This designation is useful in conditions where we are waiting for a variable to be changed in a while loop.  The C# compiler is really smart and usually assumed volatility for you, but it's a great habit to use it explicitly.  Basically what it does is it does not allow the value of a variable to be cached in a register or a stack.  Any time the value is referenced it will be pulled from it's memory location on the stack.  If this were not the case the value could change and you may never break out of the while loop.  Here is a quick example:

private volatile bool IsCanceled;

private void RunThread(object state) {
    while (!IsCanceled) {
        // Do work here!
    }
}

In the code block above, if some other thread changes the state of IsCanceled then the while loop will stop and the thread will exit.  While this is the behavior you would expect anyway the compiler might not agree with you (especially if there is late-binding or the value is modified outside of the scope of the class).  This keyword only works on fields, not properties and should only be used where it must as it will adversely affect performance.  Again, it's just good practice to use it when reading a value that can be mutated by another thread.

One of my favorite patterns is the singleton pattern.  This pattern is the most widely recognized but the least understood.  Lets take a second to examine a standard implementation of the singleton pattern.

public class SingletonExampleClass {

    private static volatile SingletonExampleClass _instance = null;

    public static SingletonExampleClass Instance {
        get {
            if (_instance == null) {
                lock (typeof(SingletonExampleClass)) {
                    if (_instance == null) {
                        _instance = new SingletonExampleClass();
                    }
                }
            }
            return _instance;
        }
    }
}

As you can see from the code above we have to implement double-checking.  The reason is that after the evaluation another thread could be slightly ahead and have created the object just ahead of you.  You could lock the type before doing your check in the first place but locking is expensive and completely unnecessary most of the times.  Only the first call requires the lock, after that all other calls simply want an a reference to that class.  Singleton is a very important pattern for serialization and is one of the most common, that's why Microsoft gave us a break here with the readonly keyword.  The same section of code above can be written as:

public class SingletonExampleClass {

    public static readonly SingletonExampleClass Instance = 
        new SingletonExampleClass();

}

Much simpler! It literally eliminated 12 lines of code!  This is more or less the same as it's brother.  The only difference is the lazy instantiation in the first example, but in almost all cases this pattern is simpler and more intuitive.  This pattern is also a little more flexible as it will allow you to use readonly on non-static fields as well.  Additionally you may instantiate the value in the constructor rather than the initializer, but you must do one or the other.  Also, once the value is set it may not be changed!

Another favorite of mine is the Interlocked class.  Interlocked.Increment and Interlocked.Decrement allows you to increment or decrement an numeric value using a thread-safe mechanism. 

private int ItemsProcessed = 0;

private void RunThread(object state) {
    List<Order> orders = state as List<Order>;
    
    if ( orders == null )
        return;

    foreach (Order ord in orders) {
        // Process Order
        // ...
        Interlocked.Increment(ref ItemsProcessed);
    }
}

As you can see, it's pretty simple to  use.  You may be wondering about the use of ref in the function signature.  Well, as you know int and long are value types (structs) and would be passed to any function by value (copying the contents to the function).  When we use the ref function signature it tells the compiler that we don't want to pass a copy of this value to the function, we want to pass the actual variable.  I.E. we passed a pointer to the variable rather than the value of it. This means that any mutations made to the variable inside of the function are effective outside of the function. 

The next basic structure I'd like to discuss is a Mutex.  A Mutex is a lot like the Monitor object I showed above and a ManualResetEvent but can work between different processes, not just different threads.  The specifics of a Mutex are best described in this snippit from the MSDN documentation:

Mutexes are of two types: local mutexes, which are unnamed, and named system mutexes. A local mutex exists only within your process. It can be used by any thread in your process that has a reference to the Mutex object that represents the mutex. Each unnamed Mutex object represents a separate local mutex.

Named system mutexes are visible throughout the operating system, and can be used to synchronize the activities of processes. You can create a Mutex object that represents a named system mutex by using a constructor that accepts a name. The operating-system object can be created at the same time, or it can exist before the creation of the Mutex object. You can create multiple Mutex objects that represent the same named system mutex, and you can use the OpenExisting method to open an existing named system mutex.

Note:

On a server that is running Terminal Services, a named system mutex can have two levels of visibility. If its name begins with the prefix "Global\", the mutex is visible in all terminal server sessions. If its name begins with the prefix "Local\", the mutex is visible only in the terminal server session where it wa s created . In that case, a separate mutex with the same name can exist in each of the other terminal server sessions on the server . If you do not specify a prefix when you create a named mutex , it takes the prefix "Local\". Within a terminal server session, two mutexes whose names differ only by their prefixes are separate mutexes, and both are visible to all processes in the terminal server session. That is, the prefix names "Global\" and "Local\" describe the scope of the mutex name relative to terminal server sessions, not relative to processes .

Here is a short sample that checks for the an existing Mutex and exits if found.  This is useful for single-instance applications.

static class Program {

    static Mutex m;

    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main() {
        // Check for single instance of our application
        bool createdNew;
        m = new Mutex(true, "TestThreadingApplication", out createdNew);
        if (!createdNew) {
            m.ReleaseMutex();
            return;
        }
        
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
        m.ReleaseMutex();
    }
}

The code works by trying to get an existing Mutex named "TestThreadingApplication".  If that Mutex does not exist in the Operating System it will be created and your thread will be assigned the owner.  If you were the first instance you will have created the Mutex and you may resume execution, otherwise your application will exit.

The last thing we will discuss in this post is a Semaphore.  A Semaphore works much the same way as a Mutex, but works best as a way to manage a pool of objects.  The Semaphore starts with an initial count of objects.  Each time the WaitOne method is called the count will be decrement.  When the count reaches zero threads will be blocked until another thread calls Release.  Unlike the Mutex, the Semaphore does not track which threads have incremented or decremented the internal count so the programmer must be careful to Release the exact number of times WaitOne is called.  It's best to think of a Semaphore as a synchronization mechanism that can let more than one thread into the critical section.  Because of it's similarity to other synchronization objects I didn't create a sample code block.

 

Links: