.NET Framework 1.1 Performance Guidelines - Locking and Synchronization

From Guidance Share

Jump to: navigation, search

- J.D. Meier, Srinath Vasireddy, Ashish Babbar, Rico Mariani, and Alex Mackman


Contents

Acquire Locks Late and Release Them Early

Minimize the duration that you hold and lock resources, because most resources tend to be shared and limited. The faster you release a resource, the earlier it becomes available to other threads.

Acquire a lock on the resource just before you need to access it and release the lock immediately after you are finished with it.


Avoid Locking and Synchronization Unless Required

Synchronization requires extra processing by the CLR to grant exclusive access to resources. If you do not have multithreaded access to data or require thread synchronization, do not implement it. Consider the following options before opting for a design or implementation that requires synchronization:

  • Design code that uses existing synchronization mechanisms; for example, the Cache object used by ASP.NET applications.
  • Design code that avoids concurrent modifications to data. Poor synchronization implementation can negate the effects of concurrency in your application. Identify areas of code in your application that can be rewritten to eliminate the potential for concurrent modifications to data.
  • Consider loose coupling to reduce concurrency issues. For example, consider using the event-delegation model (the producer-consumer pattern) to minimize lock contention.


Use Granular Locks to Reduce Contention

When used properly and at the appropriate level of granularity, locks provide greater concurrency by reducing contention. Consider the various options described earlier before deciding on the scope of locking. The most efficient approach is to lock on an object and scope the duration of the lock to the appropriate lines of code that access a shared resource. However, always watch out for deadlock potential.


Avoid Excessive Fine-Grained Locks

Fine-grained locks protect either a small amount of data or a small amount of code. When used properly, they provide greater concurrency by reducing lock contention. Used improperly, they can add complexity and decrease performance and concurrency. Avoid using multiple fine-grained locks within your code. The following code shows an example of multiple lock statements used to control three resources.

s = new Singleton();

sb1 = new StringBuilder();
sb2 = new StringBuilder();

s.IncDoubleWrite(sb1, sb2)

class Singleton
{
  private static Object myLock = new Object();
  private int count;
  Singleton()
  {
     count = 0;
  }
   public void IncDoubleWrite(StringBuilder sb1, StringBuilder sb2)
   {
      lock (myLock) 
      {
         count++;
         sb1.AppendFormat("Foo {0}", count);
         sb2.AppendFormat("Bar {0}", count);
       }
   }
   public void DecDoubleWrite(StringBuilder sb1, StringBuilder sb2)
   {
      lock (myLock) 
      {
         count--;
         sb1.AppendFormat("Foo {0}", count);
         sb2.AppendFormat("Bar {0}", count);
      }
    }
}

Note All methods in all examples require locking for correctness (although Interlocked.Increment could have been used instead).

Identify the smallest block of code that can be locked to avoid the resource expense of taking multiple locks.


Avoid Making Thread Safety the Default for Your Type

Consider the following guidelines when deciding thread safety as an option for your types:

  • Instance state may or may not need to be thread safe. By default, classes should not be thread safe because if they are used in a single threaded or synchronized environment, making them thread safe adds additional overhead. You may need to synchronize access to instance state by using locks but this depends on what thread safety model your code will offer. For example, in the Neutral threading model instance, state does not need to be protected. With the free threading model, it does need to be protected.

Adding locks to create thread safe code decreases performance and increases lock contention (as well as opening up deadlock bugs). In common application models, only one thread at a time executes user code, which minimizes the need for thread safety. For this reason, most .NET Framework class libraries are not thread safe.

  • Consider thread safety for static data. If you must use static state, consider how to protect it from concurrent access by multiple threads or multiple requests. In common server scenarios, static data is shared across requests, which means multiple threads can execute that code at the same time. For this reason, it is necessary to protect static state from concurrent access.


Use the Fine-Grained lock (C#) Statement Instead of Synchronized

The MethodImplOptions.Synchronized attribute will ensure that only one thread is running anywhere in the attributed method at any time. However, if you have long methods that lock few resources, consider using the lock statement instead of using the Synchronized option, to shorten the duration of your lock and improve concurrency.

[MethodImplAttribute(MethodImplOptions.Synchronized)]
public void MyMethod ()
//use of lock
public void MyMethod()
{
  …
  lock(mylock)
  {
   // code here may assume it is the only code that has acquired mylock 
   // and use resources accordingly
   …
  }
}


Avoid Locking "this"

Avoid locking "this" in your class for correctness reasons, not for any specific performance gain. To avoid this problem, consider the following workarounds:

* Provide a private object to lock on.

     public class A {
       …  lock(this) { … }
       …}
     // Change to the code below:
     public class A 
     {
       private Object thisLock = new Object();
       …  lock(thisLock) { … }
       …}

This results in all members being locked, including the ones that do not require synchronization.

If you require atomic updates to a particular member variable, use the System.Threading.Interlocked class.

Note Even though this approach will avoid the correctness problems, a locking policy like this one will result in all members being locked, including the ones that do not require synchronization. Finer-grained locks may be appropriate.


Coordinate Multiple Readers and Single Writers By Using ReaderWriterLock Instead of lock

A monitor or lock that is lightly contested is relatively cheap from a performance perspective, but it becomes more expensive if it is highly contested. The ReaderWriterLock provides a shared locking mechanism. It allows multiple threads to read a resource concurrently but requires a thread to wait for an exclusive lock to write the resource.

You should always try to minimize the duration of reads and writes. Long writes can hurt application throughput because the write lock is exclusive. Long reads can block the other threads waiting for read and writes.

For more information, see "ReaderWriterLock Class," on MSDN at http://msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemThreadingReaderWriterLockClassTopic.asp.


Do Not Lock the Type of the Objects to Provide Synchronized Access

Type objects are application domain-agile, which means that the same instance can be used in multiple application domains without any marshaling or cloning. If you implement a policy of locking on the type of an object using lock(typeof(type)), you lock all the instances of the objects across application domains within the process.

An example of locking the whole type is as follows.

lock(typeof(MyClass))
{
  //custom code
}

Provide a static object in your type instead. This object can be locked to provide synchronized access.

class MyClass{
 private static Object obj = new Object();
 public void SomeFunc()
 {
   lock(obj)
   {
     //perform some operation
   }
 }
}

Note A single lock statement does not prevent other code from accessing the protected resource — it is only when a policy of consistently acquiring a certain lock before certain operations is implemented that there is true protection.

You should also avoid locking other application domain-agile types such as strings, assembly instances, or byte arrays, for the same reason.

Personal tools