[C#] Multithreading in C# 3 [4] – Synchronization and Locks

You have heard many times that managing multithreading application is difficult. Let’s check one of the most common problems and how we can handle the problem.

1. Bank Account Problem

Let’s check the classic problem of multitasking – 2 people are withdrawing money from the same account.

Suppose each “withdraw” task is performed in a new thread.

public class BankAccount
{
  public decimal Balance { get; set; }
  public void Withdraw(decimal amount)
  {
    Console.WriteLine("{0} - Before: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
    Thread.Sleep(2000); // takes 2 secs
    Balance -= amount;
    Console.WriteLine("{0} - After: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
  }
}

public static class LockTest
{
  public static void Test1()
  {
    BankAccount acc = new BankAccount { Balance = 1000M }; // $1000

    // Person 1
    Thread withdraw1 = new Thread(param =>
      {
        BankAccount account = param as BankAccount;
        account.Withdraw(100M);
      });

    // Person 2
    Thread withdraw2 = new Thread(param =>
      {
        BankAccount account = param as BankAccount;
        account.Withdraw(100M);
      });

    Console.WriteLine("Balance = {0:c}", acc.Balance);

    withdraw1.Start(acc);
    withdraw2.Start(acc);

    withdraw1.Join();
    withdraw2.Join();

    Console.WriteLine("Balance = {0:c}", acc.Balance);
  }
}

You can find the final balance is $900, not $800. It is good for you but the bank will not be happy about it.

2. Locks

when a single object can be modified by multiple threads, you need to take some actions to make the object status in sync.

The most common way is to lock the part of the code. The “lock” keyword requires you to specify a token (an object reference) that must be acquired by a thread to enter within the lock scope.

public class BankAccount
{
  private object lockKey = new object();

  public decimal Balance { get; set; }
  public void Withdraw(decimal amount)
  {
    lock (lockKey)
    {
      Console.WriteLine("{0} - Before: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
      Thread.Sleep(2000); // takes 2 secs
      Balance -= amount;
      Console.WriteLine("{0} - After: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
    }
  }
}

Now the second thread should wait until the first thread exits out of the lock scope and frees the lock.

You can see the final balance is $800 now.

3. Monitor Class

The “lock” keyword is a shortcut syntax of the “System.Threading.Monitor” class.

public static class Monitor
{
  public static void Enter(Object obj);
  public static void Exit(Object obj);
}

You can rewrite the “BankAccount” class like this:

public void Withdraw(decimal amount)
{
  Monitor.Enter(lockKey);
  try
  {
    Console.WriteLine("{0} - Before: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
    Thread.Sleep(2000); // takes 2 secs
    Balance -= amount;
    Console.WriteLine("{0} - After: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
  }
  finally
  {
    Monitor.Exit(lockKey);
  }
}

The “Monitor” class provide some other methods such as “Wait()” and “Pulse()” to fine-tune the behavior of the lock but in most cases the “lock” keyword will suffice to meet your needs.

4. [Synchronization] Attribute

The “System.Runtime.Remoting.Contexts.SynchronizationAttribute” can be used to enforce a synchronization for all contexts that share the same object. You can specify the [Synchronization] Attribute to the class.

The important thing to remember is that the [Synchronization] Attribute works only for “System.ContextBoundObject” object. Your class should inherit from “ContextBoundObject” class.

[Synchronization()]
public class BankAccount : ContextBoundObject
{
  public decimal Balance { get; set; }
  public void Withdraw(decimal amount)
  {
    Console.WriteLine("{0} - Before: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
    Thread.Sleep(2000); // takes 2 secs
    Balance -= amount;
    Console.WriteLine("{0} - After: {1:c}", Thread.CurrentThread.ManagedThreadId, Balance);
  }
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s