Improving your C# Skills
上QQ阅读APP看书,第一时间看更新

Thread synchronization

In multithreaded applications, we have shared resources that are accessible by multiple threads executing simultaneously. The area where the resources are shared across multiple threads is known as the critical section. To protect these resources and provide thread-safe access, there are certain techniques that we will discuss in this section.

Let's take an example where we have a singleton class for logging a message into the filesystem. A singleton, by definition, denotes that there should only be one instance shared across multiple calls. Here is the basic implementation of a singleton pattern that is not thread-safe:

public class Logger 
{ 
  static Logger _instance; 
 
  private Logger() { } 
 
  public Logger GetInstance() 
  { 
    _instance = (_instance == null ? new Logger() : _instance); 
    return _instance; 
  } 
 
  public void LogMessage(string str) 
  { 
    //Log message into file system 
  } 
 
} 

The preceding code is a lazy initialization singleton that creates an instance on the first call on the GetInstance method. GetInstance is the critical section and is not thread-safe. If multiple threads enter into the critical section, multiple instances will be created and the race condition will occur.

The race condition is a problem in multithreaded programming that occurs when the outcome depends on the timing of events. A race condition arises when two or more parallel tasks access a shared object.

To implement the thread-safe singleton, we can use a locking pattern. Locking ensures that only one thread can enter into the critical section, and if another thread attempts to enter, it will wait until the thread is released. Here is a modified version that enables a singleton to be thread-safe:

public class Logger 
{ 
 
  private static object syncRoot = new object(); 
  static Logger _instance; 
 
  private Logger() { } 
 
  public Logger GetInstance() 
  { 
    if (_instance == null) 
    { 
      lock (syncRoot) 
      { 
        if (_instance == null) 
        _instance = new Logger(); 
      } 
    } 
    return _instance; 
  } 
 
  public void LogMessage(string str) 
  { 
    //Log message into file system 
  } 
}