Explained: Locking and Synchronization Explained

From Guidance Share

Jump to: navigation, search

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


Contents

Locking and Synchronization Explained

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.
Personal tools