Locking and Synchronization Explained,C#锁与同步

http://msdn.microsoft.com/en-us/library/ff647790.aspx#scalenetchapt05_topic15

Locking and synchronization provide a mechanism to grant exclusive access to data or code to avoid concurrent execution.

This section summarizes steps to consider to help you approach locking and synchronization correctly:

  • Determine that you need synchronization.
  • Determine the approach.
  • Determine the scope of your approach.

Determine That You Need Synchronization

Before considering synchronization options, you should think about other approaches that avoid the necessity of synchronization, such as loose coupling. Particularly, you need to synchronize when multiple users concurrently need to access or update a shared resource, such as static data.

Determine the Approach

The CLR provides the following mechanisms for locking and synchronization. Consider the one that is right for your scenario:

  • Lock (C#). The C# compiler converts the Lock statement into Monitor.Enter and Monitor.Exit calls around a try/finally block. Use SyncLock in Visual Basic .NET.
  • WaitHandle class. This class provides functionality to wait for exclusive access to multiple objects at the same time. There are three derivatives of WaitHandle:
    • ManualResetEvent. This allows code to wait for a signal that is manually reset.
    • AutoResetEvent. This allows code to wait for a signal that is automatically reset.
    • Mutex. This is a specialized version of WaitHandle that supports cross-process use. The Mutex object can be provided a unique name so that a reference to the Mutex object is not required. Code in different processes can access the same Mutex by name.
  • MethodImplOptions.Synchronized enumeration option. This provides the ability to grant exclusive access to an entire method, which is rarely a good idea.
  • Interlocked class. This provides atomic increment and decrement methods for types. Interlocked can be used with value types. It also supports the ability to replace a value based on a comparison.
  • Monitor object. This provides static methods for synchronizing access to reference types. It also provides overridden methods to allow the code to attempt to lock for a specified period. The Monitor class cannot be used with value types. Value types are boxed when used with the Monitor and each attempt to lock generates a new boxed object that is different from the rest; this negates any exclusive access. C# provides an error message if you use a Monitor on a value type.

Determine the Scope of Your Approach

You can lock on different objects and at different levels of granularity, ranging from the type to specific lines of code within an individual method. Identify what locks you have and where you acquire and release them. You can implement a policy where you consistently lock on the following to provide a synchronization mechanism:

  • Type. You should avoid locking a type (for example. lock(typeof(type)). Type objects can be shared across application domains. Locking the type locks all the instances of that type across the application domains in a process. Doing so can have very unexpected results, not the least of which is poor performance.
  • "this". You should avoid locking externally visible objects (for example. lock(this)) because you cannot be sure what other code might be acquiring the same lock, and for what purpose or policy. For correctness reasons, "this" is best avoided.
  • Specific object that is a member of a class. This choice is preferred over locking a type, instance of a type, or "this" within the class. Lock on a private static object if you need synchronization at class level. Lock on a private object (that is not static) if you need to synchronize only at the instance level for a type. Implement your locking policy consistently and clearly in each relevant method.

While locking, you should also consider the granularity of your locks. The options are as follows:

  • Method. You can provide synchronized access to a whole method of an instance using the MethodImplOptions.Synchronized enumeration option. You should consider locking at method level only when all the lines of code in the method need synchronized access; otherwise this might result in increased contention. Additionally, this provides no protection against other methods running and using the shared state — it is rarely useful as a policy, because it corresponds to having one lock object per method.
  • Code block in a method. Most of your requirements can be fulfilled choosing an appropriately scoped object as the lock and by having a policy where you acquire that lock just before entering the code that alters the shared state that the lock protects. By locking objects, you can guarantee that only one of the pieces of code that locks the object will run at a time.

Locking and Synchronization Guidelines

This section summarizes guidelines to consider when developing multithreaded code that requires locks and synchronization:

  • Acquire locks late and release them early.
  • Avoid locking and synchronization unless required.
  • Use granular locks to reduce contention.
  • Avoid excessive fine-grained locks.
  • Avoid making thread safety the default for your type.
  • Use the fine-grained lock (C#) statement instead of Synchronized.
  • Avoid locking "this".
  • Coordinate multiple readers and single writers by using ReaderWriterLock instead of lock.
  • Do not lock the type of the objects to provide synchronized access.

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.
原文地址:https://www.cnblogs.com/wucg/p/2343553.html