C# > Asynchronous Programming > Parallel Programming > Thread Safety and Synchronization
Locking for Thread Safety
This snippet demonstrates how to use the lock
keyword in C# to protect shared resources from concurrent access, ensuring thread safety when multiple threads might try to modify the same data simultaneously.
Code Example
This example showcases a simple counter class. The Increment
method increases the counter value. The lock
statement ensures that only one thread can execute the code within the lock
block at a time. _lock
is an object used as a locking mechanism. The Main
method creates multiple tasks, each incrementing the counter multiple times. Without the lock
, you would likely see incorrect results due to race conditions.
using System;
using System.Threading;
using System.Threading.Tasks;
public class Counter
{
private int _count = 0;
private readonly object _lock = new object();
public void Increment()
{
lock (_lock)
{
_count++;
Console.WriteLine($"Thread: {Thread.CurrentThread.ManagedThreadId}, Count: {_count}");
}
}
public int GetCount()
{
lock (_lock)
{
return _count;
}
}
}
public class Example
{
public static void Main(string[] args)
{
Counter counter = new Counter();
Task[] tasks = new Task[5];
for (int i = 0; i < 5; i++)
{
tasks[i] = Task.Run(() =>
{
for (int j = 0; j < 1000; j++)
{
counter.Increment();
}
});
}
Task.WaitAll(tasks);
Console.WriteLine($"Final Count: {counter.GetCount()}");
}
}
Concepts Behind the Snippet
The lock
keyword in C# is a syntactic sugar for the Monitor.Enter
and Monitor.Exit
methods. It ensures that a block of code is executed by only one thread at a time, preventing race conditions and ensuring data integrity in multi-threaded environments. It's crucial for protecting shared resources.
Real-Life Use Case
Consider a banking application where multiple threads might try to update an account balance simultaneously. Using a lock
would ensure that the balance is updated correctly, preventing overdrafts or incorrect balances due to concurrent updates. Another example is a cache server, where concurrent access and modification of cached data needs to be synchronized to maintain data consistency.
Best Practices
Interview Tip
Be prepared to explain how the lock
keyword works internally, how it relates to the Monitor
class, and the potential problems (like deadlocks) that can arise when using locks incorrectly. Also, be prepared to discuss alternatives to locks, such as lock-free data structures or using asynchronous operations with async/await
to reduce the need for explicit locking.
When to Use Locks
Use locks when you have shared resources that multiple threads need to access and modify concurrently. Locks are essential when maintaining data integrity and preventing race conditions is critical. However, consider the performance implications and explore alternative synchronization mechanisms if contention is high.
Memory Footprint
The memory footprint of a lock
statement itself is minimal, consisting mainly of the lock object itself. However, excessive locking can indirectly increase memory consumption if it leads to thread blocking and increased thread context switching overhead.
Alternatives
Alternatives to using lock
include: Interlocked
class.Mutex
.Semaphore
.ConcurrentQueue
or ConcurrentDictionary
.
Pros
lock
keyword is easy to understand and implement.
Cons
FAQ
-
What happens if I don't use a lock when accessing a shared resource?
If you don't use a lock, multiple threads might access and modify the shared resource concurrently, leading to race conditions. This can result in data corruption, inconsistent state, and unpredictable program behavior. -
How can I avoid deadlocks when using multiple locks?
To avoid deadlocks, establish a consistent lock ordering. Always acquire locks in the same order across all threads. Additionally, consider using techniques like lock timeouts to detect and recover from potential deadlocks.