Thread Safety in C#

In multithreading programming, the word “thread-safety” is often used. Thread-Safety is relevant when resources are shared between threads. In other words, when there is a  chance of two concurrent threads accessing a shared resource simultaneously. Examples may include multiple threads accessing a shared database, for instance, or updates to a set of system variables. This behavior is also known as a race condition. A race condition exists when a thread modifies a resource to an intermediate state, and then another thread attempts to access that resource and use it in the intermediate state.

Let’s understand it with an example as in the following:

  1. public class BankAccount  
  2. {  
  3.    public int TotalAmount = 0;  
  4.    public void AddAmount(int amount)  
  5.    {  
  6.       TotalAmount += amount;  
  7.    }  
  9.    public void RemoveAmount(int amount)  
  10.    {  
  11.       TotalAmount -= amount;  
  12.    }  
  13. }  
This class represents a banking account of a person with the functionality of crediting and debiting his total amount with a specific amount. Let’s consider the situation where two threads simultaneously attempt to access the same instance of the BankAccount class. One thread might call AddAmount at the same time that the second thread called RemoveAmount. In that case, the value of TotalAmount could be changed by the second thread before an accurate value could be set by the first thread. This race condition can cause inconsistent results to be reported and can cause data to get corrupted.

Using locks as a solution to Race Condition:

The purpose of the lock statement is to prevent the same block of code from being run at the same time on different threads. You can only obtain a lock on an object that returns a reference. A value type cannot be locked with this approach.

The code between the curly braces define the scope of the lock statement. When execution reaches the lock statement, if the specified object is currently locked, the thread requesting the lock is blocked and the code is suspended at this point. When the thread that currently holds the lock reaches the closing curly brace of the lock statement, the lock is released, enabling the blocked thread to acquire the lock itself and continue.
  1. lock (MyObject)  
  2. {  
  3.    // Insert code that affects MyObject.  
  4. }  
Disadvantages of Locks:

Using locks in this manner is safe but can degrade performance to a great extent. Performance may be worsened with a program with many different running threads. If each thread requires access to a specific object and must wait to obtain an exclusive lock on that object before executing, the threads will all cease executing and back up behind one another, causing poor performance. Due to this, locks are recommended when code is executed as a unit.

On the other hand, a deadlock might occur when multiple threads wait for each other to release shared resources. For example, Thread 1 might hold a lock on resource A and is waiting for resource B. Thread 2, on the other hand, might have a lock on resource B and awaits resource A. In such a case, neither of the threads will be allowed to proceed. Deadlock situations can only be avoided through careful programming.

Reference: Thread-Safe Components.