Lock keyword in C# | Thread Locking In C#

The lock keyword in C# is used to place around a critical section of code, where we want to allow only one thread to access the resource at a time. Any other thread cannot access the lock and it waits for the lock to be released.

In this post series, we will go through the understanding of the lock keyword,  monitor, mutex, and semaphore available in C#.

All of these classes (lock, monitor, mutex, and semaphore) provide a synchronization mechanism to protect the shared code or resources in a multithreaded application.

CSharp Lock keyword in multithreading
C# Lock keyword

Why do we use a lock statement in C#?

C# Lock Keyword: In C#, the locking is a synchronization mechanism that allows only one thread to access a specific piece of code or a common field at a time. The lock keyword is mainly used in the multithreded environment to implement exclusive locks to avoid inconsistent results when reading and writing public variables.

Syntax

The syntax for defining a lock for a specific section of code is as follows:

Lock (expression) { statement block }
 // Syntax to define the lock keyword
lock (obj)
 {
   // Critical code section
 }
Lock in C#
Lock keyword in C#

Example of Lock keyword In C#

In the following example, we are using the lock keyword around a critical section of code. This means that only one thread is allowed to enter and execute the code at a time.

using System;
using System.Threading;
namespace LockStatementDemo
{
    class Program
    {
        static readonly object _lock = new object();
        static void Main(string[] args)
        {          
         // Creating threads
         Thread thread1 = new Thread(PrintCharacter);
         Thread thread2 = new Thread(PrintCharacter);
        // Executing the tasks
         thread1.Start();
         thread2.Start();

         Console.ReadLine();
        }
        public static void PrintCharacter()
        {
            string strArray = "Hello World";
            lock (_lock)
            {
                for (int i = 0; i < strArray.Length; i++)
                {
                    Console.Write($"{strArray[i]}");
                    //Pausing the thread execution for 2 seconds
                    Thread.Sleep(TimeSpan.FromSeconds(2));
                }
            }
            Console.Write(" ");
        }
    }
}

In the above code, we have created two separate threads thread1, and thread2 in the main method to call the static method PrintCharacter simultaneously.

Here, we are using the lock statement on the variable of type object called ‘_lock’ to acquire the exclusive lock on the for loop statement.
It will allow only one thread to enter into the critical section to execute the code at a time.

Let’s run the above program to see the result.

Multithreading using lock

In the above result, we can see that the lock statement blocked the second thread to execute the code until the first thread released the object.

Example: Multithreading Without Lock Statement

In the following example, multiple threads are executing the critical section of code simultaneously without using the lock statement.

using System;
using System.Threading;
namespace Multithreading
{
    class Program
    {    
        static void Main(string[] args)
        {          
         // Creating threads
         Thread thread1 = new Thread(PrintCharacter);
         Thread thread2 = new Thread(PrintCharacter);
        // Executing the tasks
         thread1.Start();
         thread2.Start();

         Console.ReadLine();
      
        }
        public static void PrintCharacter()
        {
            string strArray = "Hello World";
           
                for (int i = 0; i < strArray.Length; i++)
                {
                    Console.Write($"{strArray[i]}");
                    //Pausing the thread execution for 2 seconds
                    Thread.Sleep(TimeSpan.FromSeconds(2));
                }
           
            Console.Write(" ");
        }
    }
}

Multithreading without lock keyword
The output of the above program

In the above result, we can see that both thread1 and thread2 are executing the same piece of code simultaneously.

This is why we use the lock statement to implement exclusive locks to avoid inconsistent results when reading and writing public variables in a multithreaded environment.

In C#, the lock statement internally wraps the Monitor.Enter and Monitor.Exit methods, with additional try/finally block.

Please refer to this link to learn more about the Monitor class:

 lock   Monitor.Enter  +  Monitor.Exit +  try/finally 

Can we use the lock statement with a value type?

The answer is no. We cannot pass a value type variable in the lock statement; trying to do so will result in a compile-time error message.

 class Program
    {
        static int _lock;
        static void ReadFile()
        {
            lock (_lock)
            {
                // Code
            }
        }
     }
lock keyword on value type
lock keyword with value type

Important Points:

Do not use the lock keyword in the following situations:

  • Avoid using the lock keyword with value type
  • Avoid using the this keyword as a lock expression
  • Don’t lock the type object
  • Avoid string objects, including string literals, as those, might be interned.
  • Avoid locking on anything publicly accessible in the program.

01. Avoid using the lock keyword on the value type

Use the lock keyword on reference type objects instead of value types; otherwise, you will get a compile-time error.

02. Avoid using the ‘this’ keyword as a lock expression

Use private reference type variables instead of this keyword (Example: lock(this)) to avoid deadlock situations when multiple threads are waiting to start the same object.

Using this in a lock statement is not recommended because it will block the entire object.
All other parts of the code will be blocked and will wait to execute for no reason, which may cause a performance issue as well.

03. Avoid using the ‘lock keyword’ on string object

Avoid using lock statements on string objects, because the interned strings are essentially global in nature and may be blocked by other threads without your knowledge, which can cause a deadlock situation.

Best practices for using lock keyword

  • Always use the lock keyword when accessing shared resources. This ensures that only one thread is allowed to access the shared resource at a time, preventing race conditions and other synchronization-related bugs.
  • Always use a private object as the lock. This ensures that only the code that defines the lock can use it, and no other code is allowed to access it.
  • Always use the try-finally block when using the lock keyword, to make sure that lock is always released.

The try-finally block is used to ensure that the lock is always released, regardless of whether an exception is thrown inside the try block.
The try block contains the code that accesses the shared resource and the finally block contains the code that releases the lock.

Example:

class Counter
{
    private int _count;
    private object _lock = new object();
 
    public void Increment()
    {
        lock (_lock)
        {
            try
            {
                _count++;
            }
            finally
            {
                Monitor.Exit(_lock);
            }
        }
    }
 
    public void Decrement()
    {
        lock (_lock)
        {
            try
            {
                _count--;
            }
            finally
            {
                Monitor.Exit(_lock);
            }
        }
    }
}

Lock vs Monitor

Lock keywordMonitor Class
The lock statement is more readable, it makes the code more clear and easy to understand.The Monitor class is less readable and requires more code to achieve the same functionality as the lock statement.
The lock keyword uses the System.Threading.Monitor class internally to implement the mutual-exclusion (mutex) lock.The Monitor class is a low-level synchronization primitive that gives you more control over the lock than the lock keyword.
using lock you acquire a lock on an object and automatically release it when the code execution exists the block.Monitor.Enter method is used to acquire a lock and the Monitor.Exit method is used to release the lock. It requires more care in handling the lock.
lock statement with a try-finally block ensures that the lock is always released, even if an exception occurs.Using the Monitor class, it’s possible to forget to call Monitor.Exit and the lock will be held indefinitely. So, you need to be more careful when using it
Lock vs Monitor

Conclusion

The  lock  statement is basically used to protect a shared resource from being accessed by the multiple threads in a multithreaded environment. It allows only one thread to access a  critical section of code at a time to avoid the race condition.

I hope you found this post useful in learning about the use of the lock keyword in C#. Your comments, suggestions, and constructive criticism are much appreciated.

References MSDN: lock statement (C# reference)

Recommended Articles

5 1 vote
Article Rating
Subscribe
Notify of
guest

0 Comments
Inline Feedbacks
View all comments