+ All Categories
Home > Documents > Fundamentals of Concurrent Programming for .NET

Fundamentals of Concurrent Programming for .NET

Date post: 30-May-2018
Category:
Upload: charteris-plc
View: 214 times
Download: 0 times
Share this document with a friend

of 30

Transcript
  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    1/30

    2005 Charteris plc

    Charteris W hite Paper

    Fundamentals of Concurrent

    Programming for .NET

    Greg Beech ([email protected])

    18 March 2005

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    2/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 2 of 30

    CONTENTS

    CONTENTS 21. INTRODUCTION 32. TH READING IN .NET 42.1 Operating system threads 42.2 Fibers 42.3 Managed threads 42.4 The managed thread pool 52.5 Asynchronous method invocation 6

    2.5.1Predefined asynchronous operations 62.5.2User-defined asynchronous operations 7

    2.6 Events and multicast delegates 72.7 Summary 83. TH READ SYNCHRON IZATION 93.1 Overview 93.2 The .NE T memory model 93.2.1Volatile reads and writes 93.3 Monitor and the C# lock statement 93.4 MethodImplOptions.Synchronized 113.5 ReaderWriterLock 123.6 Wait handles 13

    3.6.1Mutex 143.6.2ManualResetEvent and AutoResetEvent 153.6.3Semaphore 16

    3.7 Interlocked 163.7.1Increment and Decrement 163.7.2Exchange and CompareExchange 17

    3.8 Summary 174. DESIGN CON SIDERATIONS 184.1 Nondeterministic execution 184.2 Deadlocks 184.3 Suspending, resuming and aborting threads 204.4 Placement of critical sections 214.5 The double-lock initialization pattern 234.6 Raising events 244.7 Summary 255. CONCLUSIONS 26APPEN DIX A REFERENCE S 27APPEN DIX B SEMAPH ORE CODE LISTIN G 29

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    3/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 3 of 30

    1. INTRODUCTIONMany developers will have read Herb Sutters article The Free Lunch is Over which talks about thefuture speed increases of CPUs. The good news is that they will get significantly faster, but the badnews is that you wont see all of the possible performance gains unless you write your application totake advantage of them. Over the last few years the increase in clock speeds has slowed down, and

    chip manufacturers are focussing more and more on concurrent execution of code. Hyperthreadingwas the first step, which allows a single processor core to execute two threads in parallel, but the futureis multi-core chips which will allow many threads to execute truly independently. Intel is already talkingabout chips with over a hundred cores, so if your code is single-threaded you may only be using onehundredth of the available processing power!

    This Charteris White Paper introduces the fundamentals that will allow you to begin writingconcurrent applications, and is intended to be easier to read than much of the material on this subject.It is targeted at developers who have some experience of developing on the .NET Framework, andideally in C# as this is my language of choice for the code samples. No prior knowledge of concurrentprogramming or multi-threading is needed, though even experienced developers in this area may findsome new and surprising information.

    The first section of this paper provides background information about threads, fibers, the thread poolasynchronous methods and events particularly with respect to the .NET Framework. In the secondsection the .NET memory model is briefly examined and the most important synchronizationmechanisms such as Monitor, ReaderWriterLock and Interlocked are described. The final sectiondiscusses some of the key design patterns and considerations when writing concurrent applications.

    Please note that this paper does not to provide all the information needed to become a proficientdeveloper of concurrent applications, however it should confer a basic understanding of multi-threading and some of the choices available when writing concurrent applications. The referencessection is extensive as there is a lot of information available about this topic, and if this paper sparksyour interest I would thoroughly recommend that you read and understand the majority of thesereferenced articles which will elaborate on the concepts introduced here and introduce many moreadvanced ones.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    4/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 4 of 30

    2. TH READING IN .NET2.1 Operating system threadsAn operating system (OS) thread is a system resource that executes code in sequence. Each processrunning on the operating system has one or more threads. A computer with one single core processor

    that is not hyperthreaded can execute exactly one thread at any given time, so the operating system hasto schedule the threads to let one run for a period of time (known as its quantum or time slice)before allowing another thread to run.

    When the currently running thread is paused and another thread is allowed to run, it is known as acontext switch. Although context switches are required to allow multiple threads to run on a single

    processor they do have some performance cost such as saving and restoring registers. As such it is nota good idea to have too many threads as otherwise the system will spend more time context switchingand less time actually executing the threads. As a rough guideline, under normal conditions there maybe a few thousand context switches per second this can be monitored with the performance counterThread\Context Switches/sec using a tool such as Microsoft System Monitor.

    On a hyperthreaded CPU two threads can be executed simultaneously which can help to reduce thenumber of context switches, however there are certain compromises as it still uses the same core, sothe speed increase is not as much as you might expect with a typical increase being up to around 25%.

    A multi-processor system or multi-core processor can execute multiple threads truly in parallel andindependently of each other so the performance can theoretically increase in proportion to the numberof processors/ cores. In reality this is not quite true because of the requirement for synchronization

    between the processors in both hardware and software and the fact that it is hard to write systemsthat effectively use this many threads simultaneously! Many server applications see negligible increasesin speed past eight or even four processors.

    2.2 FibersA fiber can be thought of as a lightweight thread that it is not scheduled by Windows, but must bemanually scheduled by an application to run against a real OS thread. The Common Language Runtime(CLR) hosting API in versions 1.0 and 1.1 had basic support for fiber scheduling, and in version 2.0and later these have been comprehensively overhauled, essentially to allow the CLR to be hosted inSQL Server 2005 in fiber mode. Sophisticated hosts such as SQL Server can use fibers to improveperformance, but unless the scheduling algorithm is extremely well tuned the performance willprobably be lower than not using fibers. Unless you really need the last 10% of performance from a

    system, and you have a lot of time available, fibers are not something that you should be overlyconcerned with other than just to be aware of their existence.

    2.3 Managed threadsA managed thread is represented by the System.Threading.Thread class. In the .NET Framework1.0 and 1.1 managed threads map directly onto an OS thread, however this is not guaranteed to be thecase in the future, particularly when hosted in an environment such as SQL Server 2005 in fiber mode.Note that the .NET Framework also has the System.Diagnostics.ProcessThread class whichrepresents an OS thread although this is not particularly useful for multi-threaded programming. Thereis no relationship between the Thread and ProcessThread classes and you cannot get an instance ofone from the other.

    Every one of the main .NET languages can create and control managed threads explicitly, for example:

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    5/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 5 of 30

    static void Main(string[] args)

    {

    //create and start the thread

    Thread t = new Thread(new ThreadStart(SayHello));

    t.Start();

    //wait for the thread to finish

    t.Join();Console.WriteLine("Complete");

    }

    static void SayHello()

    {

    Console.WriteLine("Hello World");

    }

    All programs have a main application thread and in .NET applications this is the thread that runs theMain method. When a thread reaches the end of the method it starts in, the thread exits if the threadis the main application thread then normally the application will exit when it reaches the end of theMain method. In the above sample, the main application thread starts a new thread to run theSayHello method and then calls Join on it which is a blocking call that waits for the thread to exit.

    The thread t runs the SayHello method, reaches the end and then exits, so at this point the Joinmethod returns and the execution ofMain continues.

    You may have noticed that I said an application normally exits when the Main method ends this isgoverned by whether other threads that were created are foreground or background. Foregroundthreads and background threads are identical, however it is when all foreground threads have stoppedthat the application exits, so if the main application thread created any foreground threads that are stillrunning the process will not exit until they do; at this point any background threads will be terminated.The Thread class has an IsBackground property to set this (note that it can be set even once thethread has started) and you should be aware that it defaults to false.

    A final important point to consider with respect to starting new threads is security. When you create anew thread it is created with the same Code Access Security (CAS) state as the thread that created it

    this is important as it means partially trusted code cannot create new threads with full trust, but it alsomeans that the results of any assert, demand, deny etc. operations for privileges that have been madeon the original thread are propagated to the new thread.

    2.4 The managed thread poolEach OS thread that is created has supporting resources such as registers and its own execution stackwhich consumes memory. Because of this threads are relatively expensive resources to create and run.To simplify the process of creating and managing your own threads, the .NET Framework providesthe System.Threading.ThreadPool class which is a pool of threads on which work can be queuedto be asynchronously executed. The ThreadPool class automatically tunes the number of threads itcontains based on factors such as how much work is queued and CPU utilisation. The following codeshows how to queue a method call to run in the managed thread pool.

    static void Main(string[] args)

    {

    //queue the item

    bool queued = ThreadPool.QueueUserWorkItem(new WaitCallback(SayHello));

    Console.WriteLine("Queued? {0}", queued);

    //sleep to wait for it to complete

    Thread.Sleep(1000);

    Console.WriteLine("Complete");

    }

    static void SayHello(object state)

    {

    Console.WriteLine("Hello World");

    }

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    6/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 6 of 30

    Here the work is queued in the thread pool using QueueUserWorkItem and it will execute at somepoint in the future. The static call Thread.Sleep always applies to the thread that is running themethod it is called from, in this case the main application thread, and causes it to pause for thespecified number of milliseconds here 1000 is chosen as an arbitrary value that should give thethread pool item long enough to execute. Note that the actual number of milliseconds the threadpauses for may not be exactly the number specified as it depends on the OS thread scheduler but

    experience shows it will normally be within about ten or twenty milliseconds either way.

    The ThreadPool class also provides a method RegisterWaitForSingleObject which allows you toqueue an item in the same way, but have it wait for a WaitHandle to be signalled or a timeout toelapse before the code executes; wait handles will be discussed later in this paper.

    In the same way as managed threads, the CAS state is propagated to the thread pool thread when youqueue it using QueueUserWorkItem or RegisterWaitForSingleObject so these can be used safelyfrom both fully and partially trusted code. The ThreadPool class also provides complimentarymethods, UnsafeQueueUserWorkItem and UnsafeRegisterWaitForSingleObject, which can onlybe called from fully trusted code and do not propagate the CAS state these provide superiorperformance but have a potential security hole in that partially trusted code could be queued up andrun with full trust. Unless you find from performance testing that the safe methods are a bottleneck, I

    would suggest that you leave the unsafe versions alone.

    2.5 Asynchronous method invocationIn addition to managed threads and the thread pool, the .NET Framework allows applications toexecute concurrently by means of asynchronous method invocation. These may be predefinedasynchronous operations provided by the objects being used, or may be created manually by users ofany library. Note that some predefined operations and all user-defined asynchronous operationsexecute in the managed thread pool behind the scenes, so may be queued rather than executingimmediately.

    2.5.1 Predefined asynchronous operationsIf you have ever created a web reference, you will see that the generated proxy has not only the

    methods defined by the web service (e.g. GetCustomerData) but also asynchronous versions of thesewhich follow the naming convention BeginM ethodand EndM ethod(e.g. BeginGetCustomerData andEndGetCustomerData). Calling a web service synchronously to look up both customer and orderdetails may look something like the following:

    void DisplaySummary(int customerId)

    {

    LookupService svc = new LookupService();

    CustomerDetails cd = svc.GetCustomerData(customerId);

    OrderDetails od = svc.GetOrderData(customerId);

    Console.WriteLine("Name: {0}, Total Orders: {1}", cd.Name, od.TotalOrders);

    }

    The problem here is that the method first waits for one web service to complete, and then calls the

    other one each of these may take a couple of seconds to do the lookup and return the information.This method can be significantly enhanced by using the asynchronous versions to call the web servicesin parallel, e.g.

    void DisplaySummary(int customerId){

    //start the lookups asynchronously

    LookupService svc = new LookupService();

    IAsyncResult custResult = svc.BeginGetCustomerData(customerId, null, null);

    IAsyncResult orderResult = svc.BeginGetOrderData(customerId, null, null);

    //end the lookups and display the results

    CustomerDetails cd = svc.EndGetCustomerData(custResult);

    OrderDetails od = svc.EndGetOrderData(orderResult);

    Console.WriteLine("Name: {0}, Total Orders: {1}", cd.Name, od.TotalOrders);

    }

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    7/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 7 of 30

    Here the lookups are started together and run in parallel. The EndM ethodcalls block until the methodhas completed and so this method will block until both the lookups are done, then display theinformation. If each lookup takes two seconds then this method could now run in a total of twoseconds instead of four when the services are called in sequence.

    2.5.2 User-defined asynchronous operationsUser-defined asynchronous operations work in exactly the same way as the predefined ones, exceptthat you need to create the infrastructure for the BeginM ethodand EndM ethod calls yourself. This isdone using the System.MulticastDelegate class (implemented with the delegate keyword in C#) a delegate is similar to a C++ function pointer, except that it is type safe and references not only themethod but the instance of the object it will be called on.

    Say the lookup methods for customers and orders were not web service methods but were defined inan external library which does not contain asynchronous versions; you can create the same effect asfollows:

    //declare delegates that match the method signatures being called

    delegate CustomerDetails CustomerLookup(int customerId);

    delegate OrderDetails OrderLookup(int customerId);

    void DisplaySummary(int customerId)

    {

    LookupClass lookup = new LookupClass();

    //create the delegates

    CustomerLookup custLookup = new CustomerLookup(lookup.GetCustomerData);OrderLookup orderLookup = new OrderLookup(lookup.GetOrderData);

    //start the lookups asynchronously

    IAsyncResult custResult = custLookup.BeginInvoke(customerId, null, null);

    IAsyncResult orderResult = orderLookup.BeginInvoke(customerId, null, null);

    //end the lookups and display the results

    CustomerDetails cd = custLookup.EndInvoke(custResult);

    OrderDetails od = orderLookup.EndInvoke(orderResult);Console.WriteLine("Name: {0}, Total Orders: {1}", cd.Name, od.TotalOrders);

    }

    The code is very similar to the web method invocation, except that here you are defining and creatinginstances of delegates which reference the methods, and then run those delegates asynchronously. TheBeginInvoke and EndInvoke methods on the delegate instance are created by the compiler and assuch have a method signature that matches the synchronous method, with additional parameters addedfor a callback and a state obect.

    Using this technique you can invoke any method asynchronously, though bear in mind that it is slowerto execute a method asynchronously so it is only worth doing if the concurrency gains outweigh theoverhead incurred.

    2.6 Events and multicast delegatesOne of the common misconceptions about events is that they give you concurrency and that thehandlers run in the managed thread pool. Events run sequentially and on the same thread that raisedthe event. This is because underneath an event is really just a wrapper around an instance of theSystem.MulticastDelegate class and as such is subject to its behaviour.

    A multicast delegate is a delegate that allows multiple handlers to be registered. When the delegate isinvoked, all of the handlers are executed in the order that they were registered. The EventArgs objectthat is passed to the handlers may be modified, and the modified version will be received by the nexthandler in the chain (for this reason it is a good idea to make your EventArgs classes immutableunless you want this to occur).

    The following code demonstrates the pattern of event execution when a number of handlers areregistered to receive the event:

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    8/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 8 of 30

    delegate void MyEventHandler(object sender, TestEventArgs e);

    class TestEventArgs : EventArgs

    {

    public int Count;

    }

    class EventClass{

    event MyEventHandler TestEvent;

    public void RaiseEvent(int handlers)

    {

    for (int i = 0; i < handlers; i++)

    {

    this.TestEvent += new MyEventHandler(this.IncrementCount);

    }

    TestEventArgs e = new TestEventArgs();

    Console.WriteLine(

    "Method: thread {0}, count {1}",

    Thread.CurrentThread.GetHashCode(),

    e.Count);

    this.TestEvent(this, e); //raise the event

    Console.WriteLine(

    "Method: thread {0}, count {1}",

    Thread.CurrentThread.GetHashCode(),e.Count);

    }

    void IncrementCount(object sender, TestEventArgs e)

    {e.Count++;

    Console.WriteLine(

    "Handler: thread {0}, count {1}",

    Thread.CurrentThread.GetHashCode(),e.Count);}

    }

    The print out from this when invoked with three handlers is shown below (note that the value for thethread may be different on your system, but the hash code of a thread is guaranteed to be unique forthe lifetime of that thread). This shows beyond doubt that the handlers are executed sequentiallybefore control is returned to the method raising the event.

    Method: thread 2, count 0

    Handler: thread 2, count 1

    Handler: thread 2, count 2

    Handler: thread 2, count 3

    Method: thread 2, count 3

    The predictable behaviour of events is necessary for many scenarios such as closing a Windows Formwhere the Closing event is raised with a CancelEventArgs argument this allows handlers to set theCancel property of this to true, and then the method that raised the event can check this propertyand decide whether to continue closing the form.

    2.7 SummaryThe .NET Framework provides powerful mechanisms for writing concurrent applications. It allowsyou to create and manage your own threads, queue work packages into a self-tuning thread pool, andexecute methods asynchronously using either pre-defined functions of by creating your own delegatewrappers around the functions. Events in the .NET Framework do not give you concurrency which isnot what a lot of people expect as they are used to the asynchronous publisher/ subscriber model from

    environments such as COM+.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    9/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 9 of 30

    3. TH READ SYNCHRON IZATION3.1 OverviewWhen writing multi-threaded applications there is an almost universal need to access shared data fromdifferent threads. Without some form of synchronization between the threads the data would certainly

    be corrupted as one thread may overwrite parts of the data that another thread is busy reading, or twothreads may change the same value at the same time so one of the changes is lost this is known as arace condition.

    The most common approach to synchronizing the threads is through the use of locks. In lock-basedprogramming the general principle is that an object is used as a lock, and the thread must acquire thisobject before it can execute a particular region of code that reads or modifies shared data; thisprotected region is known as a critical section. There is another approach which is gatheringmomentum known as lock-free programming, which as the name suggests does not use locks at all,although it has little mainstream value at the moment and as such is not discussed in this paper (thereare however a number of references in Appendix A if you would like to find out more).

    3.2 The .NET memory modelBefore discussing synchronization mechanisms it is necessary to take a brief look at the .NET memorymodel to understand how it affects them. In order to make applications run faster, instructions such asreads and writes may be re-ordered by either compilers or hardware according to a set of rules. Therules for the .NET Framework can be summarised as follows:

    Reads and writes from the same processor to the same location cannot cross one another. No memory read (volatile or regular to any memory location), can move before a lock acquire. No memory write (volatile or regular to any memory location), can move after a lock release. All other reads and writes can reorder arbitrarily such that within a single thread of execution all

    side effects and exceptions are visible in the order specified (note that volatile reads and writes are

    considered side effects).Be aware that in these rules the only lock that is considered is the System.Threading.Monitor class;any other type of locking mechanism does not provide any guarantees about instruction reordering.

    3.2.1 Volatile reads and writesA volatile field (declared as volatile in C# ) is always read and written such that any changes areimmediately visible to any thread on any processor. It also disables certain optimisations like restrictingthe way instructions around the fields may be re-ordered. Because of the restrictions around volatilefields, accessing them is orders of magnitude slower than non-volatile fields and therefore avoidingtheir use unless they are absolutely necessary is good for performance.

    In some cases, for example when a variable is initialised only once, it may be beneficial to use one of

    the static methods on the Thread class to mimic volatility. This class provides VolatileRead andVolatileWritewhich perform volatile reads and writes respectively, and also MemoryBarrier whichcauses all data that has been written up to that point (both volatile and non-volatile fields) to be visibleto all threads on all processors the use ofMemoryBarrier is illustrated throughout the remainder ofthis paper.

    3.3 Monitor and the C# lock statemen tThe most basic and common type of synchronization uses the System.Threading.Monitor class; itis so commonly used that many languages have keywords to encapsulate the major functions Enterand Exit (lock in C#, SyncLock in Visual Basic .NET). An object is designated as the lock object;the monitor obtains a lock on this for the duration of the critical section and then releases it at the end.

    To illustrate the use of the lock statement, consider the process of adding an item to a collection. The

    following code sample illustrates a method for doing this that is not thread-safe (note that code relating

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    10/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 10 of 30

    to error checking, resizing the collection etc. is not shown throughout this paper as the aim is toillustrate threading issues rather than how to implement fully functional code).

    public class MyCollection{

    private readonly object syncRoot = new object();

    private int count = 0;

    private object[] items = new object[16];

    public int Add(object item)

    {int index = this.count;

    this.items[index] = item;

    this.count++;

    return index;

    }}

    To add the item there are two changes to the class state required: the object must be inserted into thearray of items and the count must be incremented. If two threads could execute this code at the sametime then a possible sequence of events might be as follows:

    1. Thread A gets the index and stores the item.2. A context switch occurs to let thread B run.3. Thread B gets the index and stores the item.4. Thread B increments the count and returns the index.5. A context switch occurs to let thread A run.6. Thread A increments the count and returns the index.

    In this case, both threads store their items at the same index in the array, so the item that was writtenby thread A is lost. In addition, both threads increment the count so now the count is actually onemore than the number of items in the collection. To make this method thread-safe we could rewrite it

    as follows using the Monitor class:public int Add(object item)

    {

    int index;

    Monitor.Enter(this.syncRoot);

    try

    {

    index = this.count;

    this.items[index] = item;

    this.count++;

    }

    finally

    {

    Monitor.Exit(this.syncRoot);

    }return index;

    }

    The code between Monitor.Enter and Monitor.Exit is a critical section, enclosed by a lock on theprivate object syncRoot. There are a couple of important points in this code:

    The lock is taken on a private object, not a publicly visible object such as the class itself. It is quitecommon to see statements such as lock(this) in code, which can lead to major problems ifanother (possibly totally unrelated) class decides also to lock on the class instance. Even worse islocking on types, e.g lock(typeof(MyCollection)), which is commonly used for static methods types are app-domain agile so you may be inadvertently locking a type in another app-domain!

    The lock is released in a finally block to ensure that even under error conditions it is released. Ifthe lock was not released then no other thread could ever enter this method.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    11/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 11 of 30

    Using the C# lock keyword this method can be simplified significantly. The following code will beexpanded by the compiler to be exactly equivalent to the above method:

    public int Add(object item){

    int index;

    lock (this.syncRoot)

    {index = this.count;

    this.items[index] = item;

    this.count++;} //lock is released here even under error conditions

    return index;

    }

    It is important to note that it is not only the Add method that must be synchronized the collection isalso likely to have other operations such as a Count property, an indexer, and IndexOf and Removemethods, each of which need to be synchronized. When synchronizing each of these the same lockobject must be used as otherwise although the Add method would be synchronized the Removemethod could alter the state in parallel. Correctly synchronized IndexOf and Remove methods areshown below:

    public int IndexOf(object item)

    {

    int index = -1;

    lock (this.syncRoot)

    {

    for (int i = 0; i < this.count; i++)

    {

    if (object.Equals(item, this.items[i]))

    {

    index = i;

    break;

    }

    }

    }

    return index;}

    public void Remove(object item)

    {

    lock (this.syncRoot)

    {

    int index = this.IndexOf(item);

    if (index != -1)

    {

    this.RemoveAt(index); //implementation not shown

    }

    }

    }

    These methods lead to a final interesting point about the Monitor class. Note that when Remove iscalled it takes a lock, and then calls IndexOf, which also takes a lock on the same object. This worksbecause a thread is allowed to lock the same object multiple times; it is only other threads that cannotacquire the lock. Note though that the thread must release the lock as many times as it acquires itbecause the .NET Framework tracks the number of time a lock has been taken on an object, not justwhether it is locked.

    3.4 MethodImplOptions.SynchronizedThe .NET Framework includes the System.Runtime.CompilerServices.MethodImplAttributeattribute, which can be used with the enumeration value MethodImplOptions.Synchronized toindicate to the compiler that access to the entire method should be synchronized. This is, in effect, adeclarative way to use the Monitor class to synchronize access to the method without any need for

    imperative programming, e.g.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    12/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 12 of 30

    [MethodImpl(MethodImplOptions.Synchronized)]

    public int Add(object item)

    {

    int index = this.count;

    this.items[index] = item;

    this.count++;

    return index;

    }

    The behaviour of applying the Synchronized option to a method is not adequately described inMSDN because it does not describe what it is synchronizing with respect to. The answer, which isdefined in the ECMA specification, is that the lock object is either the this pointer for instancemethods, or the Type of the class for static methods. As noted previously both of these are badpractice so I recommend you avoid this form of synchronization.

    The tricky thing about avoiding this synchronization method, however, is that you may not realise youare using it! If you use the short form of event declaration in C# , as shown below, then the compiler-generated methods to add and remove handlers are automatically synchronized using this method.

    public event EventHandler MyEvent; //short form of event syntax

    There is no requirement in the ECMA specification for this behaviour; my assumption is that it is doneas a reasonable compromise for the concise syntax as it is better to lock on the class type or instancethan nothing in a multi-threaded environment. The good news is that if you want to be really safe youcan explicitly code your own methods to add and remove handlers (which will not automatically bemade synchronized) and lock on a private object in those:

    private readonly object syncRoot = new object();

    private EventHandler myEvent;

    public event EventHandler MyEvent //full form of event syntax

    {

    add

    {

    lock (this.syncRoot){

    this.myEvent = (EventHandler)Delegate.Combine(this.myEvent, value);

    }

    }

    remove{

    lock (this.syncRoot)

    {

    this.myEvent = (EventHandler)Delegate.Remove(this.myEvent, value);

    }

    }

    }

    3.5 ReaderWriterLockFor objects which store state that is retrieved more often that it is written to, such as a

    Hashtable

    mainly used for lookups, the Monitor class may not be the most efficient way of controlling access.The System.Threading.ReaderWriterLock class allows multiple threads to read state concurrently,or a single thread to write state. For example, the previously considered Add and IndexOf methodscould be synchronized with a ReaderWriterLock as shown below:

    public class MyCollection

    {

    private readonly ReaderWriterLock rwlock = new ReaderWriterLock();

    private int count = 0;

    private object[] items = new object[16];

    public int Add(object item)

    {

    int index;

    this.rwlock.AcquireWriterLock(Timeout.Infinite);

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    13/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 13 of 30

    try

    {

    index = this.count;

    this.items[index] = item;

    this.count++;

    Thread.MemoryBarrier();

    }

    finally{

    this.rwlock.ReleaseWriterLock();

    }

    return index;

    }

    public int IndexOf(object item)

    {

    int index = -1;

    this.rwlock.AcquireReaderLock(Timeout.Infinite);

    try

    {

    for (int i = 0; i < this.count; i++)

    {

    if (object.Equals(item, this.items[i])){

    index = i;

    break;

    }

    }}

    finally

    {

    this.rwlock.ReleaseReaderLock();

    }return index;

    }

    }

    As when using the Monitor class, the lock is released in a finally block to ensure that even undererror conditions it is always released. In this collection either multiple threads can retrieve the index ofan item simultaneously, or exactly one thread can add an item. Note that I have used a memory barrierin the Add method to ensure that all writes occur before the writer lock is released asReaderWriterLock does not provide the same guarantees about writes not crossing the lockboundary as Monitor.

    Note that reader and writer threads waiting for the lock are queued separately. When a writer lock isreleased, all queued reader threads at that instant are granted reader locks. When all of those readerlocks have been released the next writer thread in the writer queue is granted a writer lock. Thisalternating behaviour between a collection of readers and a single writer ensures fairness in accessingthe resource, and that neither reader nor writer threads are starved of access.

    3.6 Wait handlesA wait handle is a synchronization object that uses signalling to control the progress of threads ratherthan a lock object. The .NET Framework 1.0 and 1.1 provide three types of wait handle in theSystem.Threading namespace Mutex, ManualResetEvent and AutoResetEvent all of whichderive from the abstract base class System.Threading.WaitHandle. Version 2.0 also provides aSemaphore class; if you do not have access to this then I have provided a code listing for a semaphorein Appendix B which has a similar public interface and behaviour.

    All wait handles have a commonality in that you can call the instance method WaitOne to wait for thatparticular instance, or the WaitHandle class has static methods WaitAll and WaitAny to wait for allor any of a number of wait handles respectively note that the wait handles may be different types.The different behaviours of the wait handles will be discussed in the context of the instance methodWaitOne; the behaviour of the static methods can be inferred from this.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    14/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 14 of 30

    When using any of the wait handles remember that they all implement the System.IDisposableinterface so you must make any class that holds one as a member also implement the disposable designpattern and close the wait handle when the class is disposed.

    3.6.1 MutexThe System.Threading.Mutex class serves much the same purpose as the Monitor class, in that it is

    designed to allow only one thread to execute a piece of code at any one time, but Mutex can be passedbetween app-domains and even used between processes. Note, however, that Mutex does not providethe same guarantees of read and write ordering as Monitor so you need to use a memory barrier whenchanging shared resources. The following code shows how the Add method of a collection could bewritten using Mutex.

    public class MyCollection : IDisposable

    {private readonly Mutex mutex = new Mutex();

    private int count = 0;

    private object[] items = new object[16];

    public int Add(object item){

    int index;this.mutex.WaitOne();

    try

    {

    index = this.count;

    this.items[index] = item;this.count++;

    Thread.MemoryBarrier();

    }

    finally

    {this.mutex.ReleaseMutex();

    }

    return index;

    }}

    The WaitOne method is called on mutex which blocks until it is signalled by another thread callingReleaseMutex; when this call returns the thread that called it owns the mutex. Similarly to the othersynchronization objects, always call the ReleaseMutex method in a finally block to ensure it iscalled even under error conditions. ReleaseMutex must be called by the same thread that was releasedby WaitOne otherwise a System.ApplicationException is thrown.

    To use a mutex between processes it must be a named instance, not an anonymous one as shownabove; the name may be specified in the constructor. A common use of named mutexes is to onlyallow one instance of a process to run at any one time, as shown below:

    class Program

    {

    private static readonly Mutex mutex;

    static void Main(string[] args)

    {

    bool createdMutex;

    mutex = new Mutex(false, "MyApplicationName", out createdMutex);

    if (!createdMutex)

    {Console.WriteLine("An instance of this app is already running.");

    return;

    }

    //run the program here}

    }

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    15/30

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    16/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 16 of 30

    3.6.3 SemaphoreA Semaphore is used to limit the number of threads simultaneously accessing a resource; generally thiswill be when more than one thread can simultaneously access the resource as otherwise a Mutex wouldbe more appropriate. As such, a semaphore is normally used to control access to read-only resources.A semaphore is created with an initial count and a maximum count. When the WaitOne method is

    called, if the count is greater than zero the count is decremented and the method returns, else if thecount is zero then the method will block until it times out or Release is called on the semaphore byanother thread (which increments the count by a specified number).

    The following class shows how an application might limit the number of users logged in at any onetime. When the user logs in StartSession is called, and if the count is greater than zero then true isreturned, else false is returned indicating that the maximum number of users are already logged in.When the user logs out FinishSession is called to increment the count on the semaphore.

    public class UserManager

    {

    private static Semaphore semaphore = new Semaphore(10, 10);

    public static bool StartSession(int timeout)

    { return semaphore.WaitOne(timeout, false);

    }

    public static void FinishSession()

    {

    semaphore.Release(); //releases a single count}

    }

    It is important that the client code calling these methods does so in the same try/finally pattern aswith other synchronization mechanisms to ensure that an acquired lock is always released, e.g.

    void RunSession()

    {

    if (UserManager.StartSession(1000)){

    try

    {

    //run the session

    }finally

    {

    UserManager.FinishSession();

    }

    }

    else

    {

    Console.WriteLine("Maximum number of users already logged on.");

    }

    }

    3.7 InterlockedThe System.Threading.Interlocked class is not designed to protect critical sections, but is forsimple atomic operations such as incrementing/ decrementing values and exchanging the values ofitems. Note that the results of all methods on the Intelocked class are immediately visible on allthreads on all processors, so no lock or memory barrier is needed.

    3.7.1 Increment and DecrementThese are used simply to increment or decrement a value in an atomic and thread-safe manner; inaddition it returns the previous value of the item before the increment or decrement occurred. Thefollowing code shows an example:

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    17/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 17 of 30

    int counter = 0;

    int previous = Interlocked.Increment(ref counter);

    //do something with the original value

    3.7.2 Exchange and CompareExchangeThe Exchange method allows you to exchange one item for another, and also retrieve the previous

    value of the item, all as an atomic operation.object original = new object();

    object replacement = new object();

    object previous = Interlocked.Exchange(ref original, replacement);

    //do something with the previous value

    Note that the usefulness of this method is somewhat limited by the fact that the original object ispassed as a reference parameter and therefore must be exactly of type int, float or object (the threetypes allowed by overloads of the method in .NET 1.0 and 1.1), it cannot be a type derived from them.The following code will fail to compile with error CS1503 (cannot convert type ref MyObject to refobject).

    MyObject original = new MyObject();

    MyObject replacement = new MyObject();

    MyObject previous = (MyObject)Interlocked.Exchange(ref original, replacement);

    The CompareExchange method is very similar to Exchange, except it also allows you to compare theitem to another to see if the exchange should occur, also as an atomic operation. If the value alreadystored does not match the comparand the exchange does not happen. The following code onlychanges the original value if it is equal to 1234:

    int original = 1234;

    int comparand = 1234;

    int replacement = 5432;

    int previous = Interlocked.CompareExchange(ref original, replacement, comparand);//do something with the previous value

    Again the CompareExchange method is limited by the fact that the original object is passed as areference parameter. It would seem logical to include generic versions of these methods in .NET 2.0and later (i.e. Interlocked.Exchange and Interlocked.CompareExchange) but they are notevident at the time of writing.

    3.8 SummaryThe .NET Framework provides many different ways to synchronize threads including basic locks,attributes, reader/ writer locks, mutexes, signalled events, semaphores and interlocked operations. Eachhas its benefits, drawbacks and limitations so it is important to choose the correct mechanism to suiteach place in your application. Concurrency in .NET is made more difficult by the weak memorymodel; one of the consequences of it is that only Monitor and Interlocked guarantee to make theresults of any changes made to shared resource visible to all other threads, so with any othersynchronization method you must use a memory barrier to manually achieve this effect before

    releasing the lock.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    18/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 18 of 30

    4. DESIGN CON SIDERATIONS4.1 N ondeterministic executionOne of the features of concurrent applications is that they have nondeterministic execution; that is thecontrol flow of the application could be different each time the application is run. The code sample

    below illustrates nondeterministic execution as there is no way of knowing the order in which the filesin the source directory will be copied to the destination directory, or by which thread.

    class MultiThreadedCopy

    {

    private string destination;

    private int index;

    private string[] files;

    public void CopyDir(string source, string destination, int threadCount)

    {

    this.destination = destination;

    this.index = 0;

    this.files = Directory.GetFiles(source);

    Thread.MemoryBarrier(); //ensure the values are set across all CPUs

    Thread[] threads = new Thread[threadCount];for (int i = 0; i < threads.Length; i++)

    {

    threads[i] = new Thread(new ThreadStart(this.CopyThreadStart));

    threads[i].Start();

    }

    for (int i = 0; i < threads.Length; i++)

    {

    threads[i].Join();

    }

    }

    void CopyThreadStart()

    {

    int i;

    while ((i = Interlocked.Increment(ref this.index)) < this.files.Length)

    {

    //copy the file at the index i in the array of files

    }

    }

    }

    In some cases such as this sample it may not matter that the execution is nondeterministic however inother cases the output may have to be deterministic for example if this application split each file intosections and used multiple threads per file then it would be important that the sections of the file werereassembled in the same order!

    The overall execution of any multi-threaded application will be nondeterministic because it depends onfactors outside the control of the programmer such as thread scheduling and often user input or

    communication with external applications. Nonetheless, it is important to understand whether anyportions of the application do require deterministic execution and use appropriate means to ensurethis; the easiest way is to write that portion as single threaded but if it is in a performance critical areathen more involved approaches may be required.

    4.2 DeadlocksA deadlock is a condition where two or more threads are blocked, each waiting for the other to takesome action. Deadlocks arise primarily as a result of a single shared resource having two or more locksthat protect it and where two threads have each acquired one of the locks and are waiting for the otherthread to release the other lock so they can continue. The following code shows a class where thissituation could arise.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    19/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 19 of 30

    class SymbolTable

    {

    private BinaryTree tree = new BinaryTree();

    private Hashtable table = new Hashtable();

    public int Add(Symbol symbol)

    {

    lock (this.tree){

    lock (this.table)

    {

    //add the symbol

    }

    }

    }

    public void Remove(Symbol symbol)

    {

    lock (this.table)

    {

    lock (this.tree)

    {

    //remove the symbol}

    }

    }

    }

    This class has two data structures, a binary tree and a hashtable, and provides methods to add andremove an item from them. The Add method first locks tree then table, whereas the Remove methodlocks table then tree. If two threads were to execute these methods simultaneously then Add couldlocktree and Remove could locktable; at this point each thread would be waiting for the other torelease the resource it is trying to lock on and neither thread can continue this is a deadlock.

    In the above example it is quite easy to see how the deadlock can arise and the fix is also simple, justchange both methods to always lock the objects in the same order and then no deadlock can arise.Unfortunately it isnt always this simple to find the issue: sometimes locks are taken in differentmethods and held while other methods are called, for example:

    class Table

    {

    private readonly object syncRoot = new object();

    private View view;

    public void Update()

    {

    lock (this.syncRoot)

    {

    this.view.Update();

    }

    }}

    class View

    {

    private readonly object syncRoot = new object();

    private Table table;

    public void Update()

    {

    lock (this.syncRoot)

    {

    this.table.Update();

    }

    }

    }

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    20/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 20 of 30

    Here either class may have the Update method called, but as each class is independently thread-safeand passes the call through to the Update method of the other class while still holding the lock adeadlock may arise. Although it is more disguised than above this problem still comes down to the factthat locks on resources are not obtained in the same order.

    To mitigate the risk of deadlocks it is important to consider not only what locks a single class is taking,

    but also what locks may be taken in the period those locks are held, particularly if any externalmethods are called. For any locks that have dependencies on each other, a strategy needs to be agreedon about the order in which the locks will be taken.

    4.3 Suspending, resuming and aborting threadsThe System.Threading.Thread class provides the methods Suspend, Resume and Abort whichpause the thread, make it resume after a pause, and abort it respectively. In general you should not usethese methods; they lead to all types of problems such as deadlocks and errors which cannot berecovered from.

    Consider a thread that is running a static constructor while it is suspended. Because a type, or anyinstances of it, cannot be used until the static constructor has completed running, any code that tries touse this type will block. In the best case a couple of other threads may block for a while, the thread

    running the static constructor is resumed, and all continues normally. In the worst case, the thread thatwas going to resume the one running the static constructor tries to access the type and it blocks thisis a deadlock condition as each thread is waiting on the other one before it can continue.

    In an even worse scenario, consider that the thread running the static constructor is aborted. Abortinga thread in .NET does not just stop it immediately as you might expect, it actually throws aSystem.Threading.ThreadAbortException onto the thread being aborted, and so the staticconstructor will throw an exception thus rendering the type unusable and ensure no instances of it canever be created. Remember that types are app-domain agile so you may not be affecting just the app-domain that the thread is aborted in!

    There is an additional problem with calling Abort on a thread, which is concerned with clean-up.When a thread is aborted then as an exception is thrown on the thread, catch and finally blocks are

    executed as normal except if it is already executing one in which case it will jump straight out of theblock and quite possibly not finish your clean-up. Below is an example where this could cause anunrecoverable situation:

    public int Add(object item)

    {

    int index;

    Monitor.Enter(this.syncRoot);

    try

    {

    index = this.count;

    this.items[index] = item;

    this.count++;

    }

    finally{

    //thread aborted here: ThreadAbortException thrown onto the thread

    Monitor.Exit(this.syncRoot);

    }

    return index;

    }

    If the thread is aborted and the exception is thrown where indicated, then the Monitor.Exit methodwill not execute and the lock will not be released. Unfortunately as the lock can only be released by thethread that acquired it, which has just been aborted, this lock will never be released and any thread thatcalls the Add method will block indefinitely.

    Fortunately there is generally no need to use the Suspend, Resume and Abort methods. Almost any

    thread synchronization that can be done with them can be done with wait handles, although itadmittedly does need cooperation between the running threads rather than being controlled by just one

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    21/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 21 of 30

    (for an example of using a wait handle to mimic Abort see section 3.6.2). One of the very fewscenarios where using these methods is appropriate would be in a tool such as a unit test host whereexternal code is run and the user may want to pause or abort the execution of it; as the tool has nocontrol over the external code it could not use wait handles for this.

    4.4 Placement of critical sectionsSome of the sample code in this paper has been concerned with making a collection class safe formulti-threaded operation. One of the troubles with making a class thread-safe by default is that a lot ofthe time it may run in a single-threaded environment, and so by introducing code that ensures thread-safety you are adding overhead without adding any value. Because of this you should generally make allinstance methods of your class not thread-safe by default.

    Note, however, that static methods should be thread-safe by default because this is the common designpattern in the .NET Framework and most people will expect them to be. Generally there is no extrawork to make static methods thread-safe as usually they either do not access shared state or accessread-only state and as such are intrinsically thread-safe.

    The desire to make classes fast and not thread-safe by default, but still allow them to be used easily inmulti-threaded environments, led the .NET Framework designers to the pattern seen on many of the

    collections which have a static Synchronizedmethod that returns a thread-safe copy of the collection.The design pattern is that the base method/ property is virtual and the thread-safe wrapper classoverrides it, adding synchronization code, as illustrated in the following sample:

    public class MyCollection

    {public virtual int Add(object item)

    {

    int index = this.count;

    this.items[index] = item;

    this.count++;return index;

    }

    public static MyCollection Synchronized(MyCollection collection){

    return new MySyncCollection(collection);

    }

    private sealed class MySyncCollection : MyCollection

    {

    private readonly object syncRoot = new object();

    private readonly MyCollection collection;

    public MySyncCollection(MyCollection collection)

    {

    this.collection = collection;

    }

    public override int Add(object item){

    lock (this.syncRoot)

    {return this.collection.Add(item);

    }

    }

    }

    }

    This initially seems like a great idea, until you realise that the collection isnt actually thread-safe. Yes,each operation is, but the trouble is that the operations are often not used independently. Consider thefollowing code, which uses a System.Collections.Queue to demonstrate.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    22/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 22 of 30

    Queue syncQueue = Queue.Synchronized(new Queue());

    void ProcessNextItem()

    {

    if (syncQueue.Count > 0)

    {

    object item = syncQueue.Dequeue();

    //do something with the item}

    }

    If two threads call ProcessNextItem concurrently then the following sequence could occur:

    1. Thread A checks the count, it is 1 and so it enters the if block.2. A context switch occurs to let thread B run.3. Thread B checks the count, it is 1 and so it enters the if block.4. Thread B dequeues the item and processes it.5. A context switch occurs to let thread A run.6.

    Thread A tries to dequeue the item but throws an InvalidOperationException becausethe queue is empty.

    This is a particularly nasty type of bug because it is very hard to reproduce, so may not be discovereduntil production. It is also conceptually hard to debug because the developer is operating under theimpression that the queue is thread-safe, so how can it be subject to threading problems?

    As a point of interest, new collections such as generics introduced in .NET 2.0 do not have the designpattern to allow synchronized collections because this type of bug has been introduced so commonlyas a result of it. There is also the additional drawback that the methods/ properties on the collectionhave to be virtual and therefore suffer from slower calling due to v-table lookups, and the contents ofthe methods cannot be inlined by the JIT compiler.

    The queue example illustrates why placement of critical sections is so important if you are to avoid

    synchronization problems. They need to be localized enough in the code so that they are not held forexcessive periods of time and are not synchronizing code that does not need it, but they also need tobe at a high enough level that they have an awareness of the circumstances under which they areoperating putting a critical section in the Dequeue method is not high level enough as it is unawarethat it is being called based on the value of the Count property. The code can be rewritten to be trulythread-safe by using manual locking as follows:

    Queue queue = new Queue();

    void ProcessNextItem()

    {

    bool dequeued = false;

    object item = null;

    lock (queue)

    {

    if (queue.Count > 0){

    item = queue.Dequeue();

    dequeued = true;

    }

    }if (dequeued)

    {

    //do something with the item

    }

    }

    Note that here the item variable is declared outside the lock statement so that the lock only needs to

    be held for the period of time it takes to check the count and dequeue the item; any processing on theitem is not synchronized so concurrency will be increased.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    23/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 23 of 30

    4.5 The double-lock initialization patternThe double-lock initialization pattern is probably the most commonly used design pattern in lock-based multi-threaded programming, and is used for the lazy initialization of singletons (a singleton is aclass that can only ever have one instance of itself instantiated). Lazy initialization means that the classis not instantiated until the first time it is accessed, which amongst other things can help to improve

    start-up times for applications. The pattern attempts to improve concurrency by avoiding taking locksexcept for the single case where the singleton is initialized, as follows:

    public class MyObject

    {

    private static readonly object syncRoot = new object();

    private static readonly MyObject singleton;

    private ConfigData config;

    private MyObject(){

    this.config = this.LoadConfiguration();

    }

    public static MyObject Singleton

    {get

    {

    if (singleton == null)

    {lock (syncRoot)

    {

    if (singleton == null)

    {

    singleton = new MyObject();

    }

    }}

    return singleton;

    }

    }

    }

    It checks whether the singleton is null, then locks the lock object, checks that the singleton is notnull again (as two threads could have simultaneously executed the first check but only one could havegot the lock), and then initializes the object. While this seems like a good idea, and does in fact workon strongly ordered memory model processors like x86, it is not guaranteed to work by the .NETframeworks memory model so if your application runs on a processor with a similarly weak memorymodel such as Intels IA64 architecture then your application may break!

    The reason is that the assignment of the new object reference to singleton could occur before theconstructor ofMyObject has completed running, and because the next thread coming in does not takea lock before checking whether it is null it may see an un-constructed or partially-constructed object!

    Note that declaring the singleton as volatile will not help here because it is only the assignment ofthe reference that will be affected, the construction may still happen after:

    Expected Sequence: Possible Sequence on IA64 or similar:

    It is important to highlight here that the problem only arises because this pattern avoids taking locks if you always take a lock before accessing a shared resource then your code is less likely to suffer fromunexpected side effects. As such, the simplest way to fix this issue is to remove the outer null check;because reads and writes cannot pass a lock then this is guaranteed to work. A better way to fix it thatkeeps the lock only on initialization semantics is to use a memory barrier (which reads and writes alsocannot cross) to ensure that an instance of the object is fully constructed before it is assigned to thesingleton, as shown below.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    24/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 24 of 30

    public class MyObject

    {

    private static object syncRoot = new object();

    private static MyObject singleton;

    private ConfigData config;

    private MyObject()

    {this.config = this.LoadConfiguration();

    }

    public static MyObject Singleton

    {

    get

    {

    if (singleton == null)

    {

    lock (syncRoot)

    {

    if (singleton == null)

    {

    MyObject temp = new MyObject();

    Thread.MemoryBarrier();singleton = temp;

    }

    }

    }

    return singleton;}

    }

    }

    4.6 Raising eventsThe first section of this paper asserted that events in .NET do not provide concurrency, so it may be asurprise to see a section devoted to it in design considerations. The problem is that raising events in

    .NET is not thread-safe unless you use a specific pattern to do so. The common approach, which isnot thread-safe, is as follows:

    delegate void MyEventHandler(object sender, MyEventArgs e);

    class MyConnection{

    event MyEventHandler Opened;

    public void Open()

    {//open the connection

    //ensure the event has subscribers

    if (this.Opened != null)

    {//raise the event

    this.Opened(this, new MyEventArgs());

    }

    }

    }

    The event raising syntax in C# requires that the event is compared to null to see if there are anyhandlers registered; if there are no handlers registered and you try to raise the event then aNullReferenceException is thrown. There is a subtle race condition here in that after the null check,all handlers for the event could be cleared before you raise it which would set it to null. Fortunatelythe fix is simple, you cache the event handler in a local scoped variable and then even if the actualevent is cleared the local reference will not be set to null:

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    25/30

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    26/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 26 of 30

    5. CONCLUSIONSThe .NET Framework provides powerful mechanisms for creating and controlling threads, and anumber of classes which encapsulate common synchronization techniques are included in the baseclass library. In spite of this, it is not easy to write robust and performant concurrent applications andthe difficulty is compounded by the relatively weak memory model specified for the .NET runtime.

    Problems in multi-threaded code can arise in many ways, from mistakes in code to unexpected sideeffects as a result of instruction reordering. Unfortunately due to the nondeterministic nature ofmultithreaded execution most of these problems will only arise sporadically when the application isunder load, so if you have written an application that uses concurrency you need to undertakeextensive load testing before the system goes live.

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    27/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 27 of 30

    APPE NDIX AREFERENCESTo make the references easier to use I have divided them roughly into their main subjects. I canthoroughly recommend all books and articles referenced here; they are well worth reading if you reallywant to build robust, high performance and secure concurrent applications.

    General Reference

    MSD N L ibrary, Various Contributors, 2005:

    http:/ / msdn.microsoft.com

    MSDN L onghorn A PI L ibrary, Various Contributors, 2005:

    http:/ / longhorn.msdn.microsoft.com

    Common L anguage Infrastructure Partition I: Concepts and A rchitecture, Various Contributors, 2001:

    http:/ / download.microsoft.com/ download/ f/ 9/ a/ f9a26c29-1640-4f85-8d07-98c3c0683396/ partition_i_architecture.zip

    Common L anguage Infrastructure Partition II : Metadata D efinition and Semantics, Various Contributors, 2001:

    http:/ / download.microsoft.com/ download/ 3/ f/ b/ 3fb318cb -f60c-4128-ada8-bbffb791046d/ partition_ii_metadata.zip

    Processor Evolution

    The Free Lunch is Over: A Fundamental Turn toward Concurrency in Software, Herb Sutter, 2005:

    http:/ / www.gotw.ca/ publications/ concurrency-ddj.htm

    Platform 2015: Intel Processor and Platform E volution for the N ex t Decade, Various Contributors, 2005:

    ftp:/ / download.intel.com/ technology/ computing/ archinnov/ platform2015/ download/ Platform_2015.pdf

    Platform 2015 Software: E nabling Innovation in Parallelism for the N ex t Decade, David J. Kuck, 2005:

    ftp:/ / download.intel.com/ technology/ computing/ archinnov/ platform2015/ download/ Parallelism.pdf

    Threading and Concurrency

    H yper-Threading T echnology, Intel Corporation, (undated):

    http:/ / www.intel.com/ business/ bss/ products/ hyperthreading/ overview.htm

    Introduction to Multithreading, Superthreading and H yperthreading, (unnamed), 2002:

    http:/ / arstechnica.com/ articles/ paedia/ cpu/ hyperthreading.ars

    Implementing Coroutines for .N E T by W rapping the Unmanaged Fiber A PI, Ajai Shankar, 2003:

    http:/ / msdn.microsoft.com/ msdnmag/ issues/ 03/ 09/ CoroutinesinNET/ default.aspx

    Inside .N E T Thread Pooling, Kumar Gaurav Khanna, 2002:

    http:/ / www.wintoolzone.com/ showpage.aspx?url=articles/ dotnet_inside_threadpooling.aspx

    A n Introduction to Programming with C# Threads, Andrew D. Birrell, 2003:

    http:/ / research.microsoft.com/ ~birrell/ papers/ ThreadsCSharp.pdf

    Threads, fibers, stacks & address space, Chris Brumme, 2003:

    http:/ / weblogs.asp.net/ cbrumme/ archive/ 2003/ 04/ 15/ 51351.aspx

    A synchronous Operations, pinning, Chris Brumme, 2003:

    http:/ / weblogs.asp.net/ cbrumme/ archive/ 2003/ 05/ 06/ 51385.aspx

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    28/30

    18 March 2005 Fundamentals of Concurrent Programming for .NETCharteris White Paper

    Page 28 of 30

    Security & A synchrony, Chris Brumme, 2003:

    http:/ / weblogs.asp.net/ cbrumme/ archive/ 2003/ 05/ 08/ 51396.aspx

    M odern Concurrency A bstractions for C# , Nick Benton, Luca Cardelli, Cdric Fournet, 2004:

    http:/ / research.microsoft.com/ Users/ luca/ Papers/ Polyphony%20(TOPLAS).pdf

    Questionable value of SyncRoot on Collections, Brad Abrams, 2003:

    http:/ / blogs.msdn.com/ brada/ archive/ 2003/ 09/ 28/ 50391.aspx

    E vents, D elegates and M ultithreading, Brad Abrams, 2005:

    http:/ / blogs.msdn.com/ brada/ archive/ 2005/ 01/ 14/ 353132.aspx

    M ulti-Threaded applications and A bort, careful not to k ill your statics , Justin Rogers, 2004:

    http:/ / weblogs.asp.net/ justin_rogers/ archive/ 2004/ 02/ 02/ 66537.aspx

    A synchronous Command E xecution in A DO.N E T 2.0, Pablo Castro, 2004:

    http:/ / msdn.microsoft.com/ data/ default.aspx?pull=/ library/ en-us/ dnvs05/ html/ async2.asp

    .NET memory model

    V olatile and M emoryBarrier(), Brad Abrams, 2004:

    http:/ / weblogs.asp.net/ brada/ archive/ 2004/ 05/ 12/ 130935.aspx

    The DOT N E T Memory Model, Vance Morrison, 2002:

    http:/ / discuss.develop.com/ archives/ wa.exe?A2=ind0203B&L=DOTNET&P=R375

    M emory Model, Chris Brumme, 2003:

    http:/ / blogs.msdn.com/ cbrumme/ archive/ 2003/ 05/ 17/ 51445.aspx

    Performance

    Improving .N E T A pplication Performance and Scalability, Various Contributors, 2004:http:/ / www.microsoft.com/ downloads/ details.aspx?FamilyId=8A2E454D-F30E-4E72-B531-75384A0F1C47&displaylang=en

    Lock-free programming

    Concurrent Programming W ithout L ock s, Keir Fraser, Tim Harris, 2004:

    http:/ / www.cl.cam.ac.uk/ Research/ SRG/ netos/ papers/ 2004-cpwl-submission.pdf

    L ock -Free Parallel A lgorithms: A n E xperimental Study, Guojing Cong, David Bader, 2004:

    http:/ / www.eece.unm.edu/ ~dbader/ papers/ lockfree-HiPC2004.pdf

    Static A nalysis of A tomicity for Programs with Lock -Free Synchronization, Liqiang Wang, Scott D. Stoller, 2005:

    http:/ / www.cs.sunysb.edu/ ~liqiang/ papers/ lockfree_tr.pdf

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    29/30

  • 8/14/2019 Fundamentals of Concurrent Programming for .NET

    30/30

    {

    base.Handle = NativeMethods.CreateSemaphore(

    IntPtr.Zero, initialCount, maximumCount, name);

    if (base.Handle == WaitHandle.InvalidHandle)

    {

    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

    }

    int errorCode = Marshal.GetLastWin32Error();createdNew = (errorCode != NativeMethods.ERROR_ALREADY_EXISTS);

    }

    [SuppressUnmanagedCodeSecurity]

    private sealed class NativeMethods

    {

    internal const int ERROR_ALREADY_EXISTS = 183;

    internal const uint SYNCHRONIZE = 0x00100000;

    [DllImport("kernel32.dll", SetLastError = true)]

    internal static extern IntPtr CreateSemaphore(

    IntPtr lpSemaphoreAttributes,

    int lInitialCount,

    int lMaximumCount,

    string lpName);

    [DllImport("kernel32.dll", SetLastError = true)]

    internal static extern IntPtr OpenSemaphore(

    uint dwDesiredAccess,

    bool bInheritHandle,string lpName);

    [DllImport("kernel32.dll", SetLastError = true)]

    internal static extern bool ReleaseSemaphore(

    IntPtr hSemaphore,int lReleaseCount,

    out int lpPreviousCount);

    }

    }}


Recommended