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.
Table of Contents
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
}
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.
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(" ");
}
}
}
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/finallyCan 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
}
}
}
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 keyword | Monitor 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 |
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
- C# Monitor class in multithreading with examples
- C# Events
- Multithreading in C#
- C# 10 New Features
- 10 Difference between interface and abstract class In C#
- C# Tuple
- Properties In C# with examples
- Generic Delegates in C# With Examples
- Constructors in C# with Examples
- IEnumerable Interface in C# with examples
- Access Modifiers in C#
- Var keyword in C#
- C# Private constructor with example
- C# Dictionary
- Difference Between Array And ArrayList In C#: Choosing the Right Collection - May 28, 2024
- C# Program to Capitalize the First Character of Each Word in a String - February 21, 2024
- C# Program to Find the Longest Word in a String - February 19, 2024