Best practices for .Net thread synchronization

Learn the core concepts of thread synchronization to write applications that can leverage thread safety and restrict access to a shared resource in the best way possible

thread states

Thread states

Synchronization is a concept that is used to prevent multiple threads from accessing a shared resource concurrently. You can use it to prevent multiple threads from invoking the properties or methods of an object concurrently. All you need to do is synchronize the block of code that accesses the shared resource or synchronize the calls to the properties and members of the object so that at any given point of time only one thread can enter the critical section.

This article presents a discussion on the concepts related to synchronization and thread safety in .Net and the best practices involved.

Exclusive lock

Exclusive locking is used to ensure that at any given point of time, one and only one thread can enter a critical section. You need to use the one of the following to implement exclusive locks in your application.

  • Lock -- this is a syntactic shortcut for the static methods of the Monitor class and is used to acquire an exclusive lock on a shared resource
  • Mutex -- similar to the lock keyword except that it can work across multiple processes
  • SpinLock -- used to acquire an exclusive lock on a shared resource by avoiding the thread context switch overhead

You can use the static methods of the Monitor class or the lock keyword to implement thread safety in your applications. Both the static members of the Monitor class and the lock keywords can be used to prevent concurrent access to a shared resource. The lock keyword is just a shortcut way of implementing synchronization. However, when you need to perform complex operations in a multithreaded application, the Wait() and Pulse() methods of the Monitor class can be useful.

The following code snippet illustrates how you can implement synchronization using the Monitor class.

private static readonly object lockObj = new object();

        static void Main(string[] args)

        {

            Monitor.Enter(lockObj);

                       try

            {

               //Some code

            }

                  finally

            {

                Monitor.Exit(lockObj);

            }

        }

The equivalent code using the lock keyword will look similar to this:

    private static readonly object lockObj = new object();

        static void Main(string[] args)

        {  

            try

            {

                lock(lockObj)

                {

                    //Some code

                }             

            }

            finally

            {

                //You can release any resources here

            }

        }

You can take advantage of the Mutex class to implement synchronization that can span across processes. Note that similar to the lock statement, a lock acquired by a Mutex can be released only from the same thread that was used to acquire the lock. Acquiring and releasing locks using Mutex is comparatively slower than doing the same using the lock statement.

The main idea behind SpinLock is to minimize the cost involved in context switch between threads -- if a thread can wait or spin for some time till it can acquire a lock on a shared resource, the overhead involved in context switch between threads can be avoided. When the critical section performs a minimal amount of work it can be a good candidate for a SpinLock.

Non-exclusive lock

You can take advantage of non-exclusive locking to limit concurrency. To implement non-exclusive locks, you can use one of the following.

  • Semaphore -- used to limit the number of threads that can have access to a shared resource concurrently. In essence, it is used to limit the number of consumers for a particular shared resource concurrently.
  • SemaphoreSlim -- a fast, light-weight alternative to the Semaphore class to implement non-exclusive locks.
  • ReaderWriterLockSlim -- the ReaderWriterLockSlim class was introduced in .Net Framework 3.5 as a replacement of the ReaderWriterLock class.

You can use the ReaderWriterLockSlim class to acquire a non-exclusive lock on a shared resource that would need frequent reads but infrequent updates. So, instead of a mutually exclusive lock on a shared resource that needs frequent reads and infrequent updates, you can use this class to acquire a read lock on the shared resource and an exclusive write lock on it.

Deadlocks

You should avoid using a lock statement on the type or use statements like lock (this) to implement synchronization in your application as this might result in deadlocks. Note that deadlocks can also arise if you are holding lock acquired on a shared resource for a longer period of time. You should not use immutable types in your lock statements. As an example, you should avoid using a string object as a key in your lock statement. You should avoid using the lock statement on a public type -- it is a good practice to lock on private or protected objects that are not interned. In essence, a deadlock situation occurs when multiple threads are waiting for each other to release lock on a shared resource. You can refer to this MSDN article to know more about deadlocks.

This article is published as part of the IDG Contributor Network. Want to Join?

To comment on this article and other InfoWorld content, visit InfoWorld's LinkedIn page, Facebook page and Twitter stream.
From CIO: 8 Free Online Courses to Grow Your Tech Skills
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.