C# Interview Questions

36 Questions
C# Programming

C# Programming

Web DevelopmentFrontendBackendGame Dev

Question 34

Explain the lock statement in C# and why it is used.

Answer:

The lock statement in C# is used to ensure that a block of code runs to completion without being interrupted by other threads. It is a synchronization mechanism that allows only one thread at a time to execute a particular section of code, thereby preventing race conditions and ensuring thread safety.

Key Concepts

  1. Thread Safety:

    • Ensures that shared data is accessed by only one thread at a time, preventing data corruption and inconsistent results.
  2. Monitor:

    • The lock statement is a shorthand for using the Monitor class, which provides a more complex and flexible synchronization mechanism.
  3. Critical Section:

    • The block of code within the lock statement is known as a critical section. Only one thread can execute it at a time.

Basic Syntax

lock (lockObject)
{
    // Critical section of code
}
  • lockObject is an object used to control access to the critical section. It should be a private, reference-type object that all threads can access but should not be publicly accessible to avoid unintentional lock.

Example Usage

Example 1: Simple Counter

Here is an example demonstrating the use of the lock statement to safely increment a counter from multiple threads:

using System;
using System.Threading;

public class Counter
{
    private int _count = 0;
    private readonly object _lockObject = new object();

    public void Increment()
    {
        lock (_lockObject)
        {
            _count++;
            Console.WriteLine($"Count: {_count}");
        }
    }
}

public class Program
{
    public static void Main()
    {
        Counter counter = new Counter();
        Thread thread1 = new Thread(counter.Increment);
        Thread thread2 = new Thread(counter.Increment);

        thread1.Start();
        thread2.Start();

        thread1.Join();
        thread2.Join();
    }
}

In this example:

  • _lockObject is used to ensure that only one thread can increment _count at a time.
  • The lock statement ensures that the Increment method's critical section is executed by only one thread at a time.

Why Use the Lock Statement?

  1. Prevent Race Conditions:

    • Ensures that multiple threads do not simultaneously modify shared data, which could lead to unpredictable and erroneous behavior.
  2. Consistency:

    • Maintains the consistency and integrity of shared resources by serializing access to critical sections.
  3. Simplicity:

    • Provides a simple and readable way to achieve thread synchronization compared to using the Monitor class directly.

Guidelines for Using Lock

  1. Use a Private Object:

    • Always use a private object for the lock statement to avoid external code from locking on the same object, which could cause deadlocks.
  2. Minimize the Scope:

    • Keep the critical section as short as possible to reduce contention and improve performance.
  3. Avoid Locking on Public Types:

    • Never lock on public types or instances of types like string, as they might be used by other parts of the code, leading to deadlocks or performance issues.

Example:

private readonly object _lockObject = new object(); // Correct
lock (_lockObject)
{
    // Critical section
}

// Avoid locking on public types or shared objects
lock (this) { } // Incorrect
lock (typeof(SomeClass)) { } // Incorrect
lock ("lock") { } // Incorrect

Advanced Example: Banking System

A more complex example could be a banking system where multiple threads attempt to deposit and withdraw money from a shared account:

using System;
using System.Threading;

public class BankAccount
{
    private decimal _balance;
    private readonly object _lockObject = new object();

    public BankAccount(decimal initialBalance)
    {
        _balance = initialBalance;
    }

    public void Deposit(decimal amount)
    {
        lock (_lockObject)
        {
            _balance += amount;
            Console.WriteLine($"Deposited {amount}, New Balance: {_balance}");
        }
    }

    public void Withdraw(decimal amount)
    {
        lock (_lockObject)
        {
            if (_balance >= amount)
            {
                _balance -= amount;
                Console.WriteLine($"Withdrew {amount}, New Balance: {_balance}");
            }
            else
            {
                Console.WriteLine($"Withdrawal of {amount} failed. Insufficient funds.");
            }
        }
    }
}

public class Program
{
    public static void Main()
    {
        BankAccount account = new BankAccount(1000m);

        Thread[] threads = new Thread[10];
        for (int i = 0; i < 5; i++)
        {
            threads[i] = new Thread(() => account.Deposit(100m));
            threads[i + 5] = new Thread(() => account.Withdraw(50m));
        }

        foreach (var thread in threads)
        {
            thread.Start();
        }

        foreach (var thread in threads)
        {
            thread.Join();
        }
    }
}

In this example:

  • The Deposit and Withdraw methods are synchronized using the lock statement to ensure thread safety.
  • Multiple threads can safely deposit and withdraw money without causing inconsistencies in the account balance.

Summary

The lock statement in C# is a synchronization mechanism used to ensure that a block of code runs to completion without being interrupted by other threads. It helps prevent race conditions, maintains data consistency, and provides a simple and readable way to achieve thread synchronization. By following best practices, such as using private lock objects and minimizing the scope of the critical section, you can effectively manage concurrency in your applications.

Recent job openings