In C#, the lock statement is a mechanism that prevents multiple threads from accessing a block of code at the same time, ensuring thread safety when working with shared resources. This is particularly important in multi-threaded applications where race conditions can occur.
Example
using System;
using System.Threading;
class Program {
private static int counter = 0;
private static readonly object lockObject = new object();
static void IncrementCounter() {
for (int i = 0; i < 1000; i++) {
lock (lockObject) {
counter++;
}
}
}
static void Main() {
Thread t1 = new Thread(IncrementCounter);
Thread t2 = new Thread(IncrementCounter);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("Final Counter: " + counter);
}
}
Syntax
lock (object) {
// Code to be executed by a single thread
}Table of Content
Lock in Thread
The lock statement is used to ensure that a block of code runs by only one thread at a time.
Syntax
lock (object) {
// Code to be executed by a single thread
}Example
using System;
using System.Threading;
class Program {
private static int counter = 0;
private static readonly object lockObject = new object();
static void IncrementCounter() {
for (int i = 0; i < 1000; i++) {
lock (lockObject) {
counter++;
}
}
}
static void Main() {
Thread t1 = new Thread(IncrementCounter);
Thread t2 = new Thread(IncrementCounter);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("Final Counter: " + counter);
}
}
In this example, the lock statement ensures that the counter is incremented safely by multiple threads, resulting in the expected final value of 2000.
Locking Multiple Resources
When necessary, you can lock multiple resources, but it requires careful management to avoid deadlocks.
Example
using System;
using System.Threading;
class Program {
private static int counter1 = 0;
private static int counter2 = 0;
private static readonly object lockObject1 = new object();
private static readonly object lockObject2 = new object();
static void IncrementCounters() {
lock (lockObject1) {
counter1++;
Thread.Sleep(10); // Simulating some work
lock (lockObject2) {
counter2++;
}
}
}
static void Main() {
Thread t1 = new Thread(IncrementCounters);
Thread t2 = new Thread(IncrementCounters);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
Console.WriteLine("Final Counter1: " + counter1);
Console.WriteLine("Final Counter2: " + counter2);
}
}
Output
Final Counter1: 2 Final Counter2: 2
Avoiding Deadlocks
Deadlocks can occur if multiple threads try to lock the same resources in a different order. To prevent this, always acquire locks in a consistent order.
Example
using System;
using System.Threading;
class Program {
private static readonly object lock1 = new object();
private static readonly object lock2 = new object();
static void Thread1() {
lock (lock1) {
Thread.Sleep(100); // Simulating some work
lock (lock2) {
Console.WriteLine("Thread 1 completed.");
}
}
}
static void Thread2() {
lock (lock2) {
Thread.Sleep(100); // Simulating some work
lock (lock1) {
Console.WriteLine("Thread 2 completed.");
}
}
}
static void Main() {
Thread t1 = new Thread(Thread1);
Thread t2 = new Thread(Thread2);
t1.Start();
t2.Start();
t1.Join();
t2.Join();
}
}
Note: This example may lead to a deadlock due to the inconsistent order of locks. Proper design is needed to avoid such situations.
Conclusion
The lock statement in C# is a fundamental tool for managing concurrency in multi-threaded applications. By understanding how to use locks effectively, developers can ensure data integrity and prevent race conditions. Always be mindful of potential deadlocks when locking multiple resources.