0
public class MyConfigurationData
{
   public double[] Data1 { get; set; }
   public double[] Data2 { get; set; }
}
public class MyClass
{
   private static object SyncObject = new object();
   private static MyConfigurationData = null;
   private static MyClass()
   {
      lock(SyncObject)
      {
         //Initialize Configuration Data
         //This operation is bit slow as it needs to query the DB to retreive configuration data
      }
   }
   public static MyMethodWhichNeedsConfigurationData()
   {
      lock(SyncObject)
      {
          //Multilple threads can call this method
          //I lock only to an extent where I attempt to read the configuration data
      }
   }
}

In my application I need to create the configuration data only once and use it several multiple times. In other words, I write once and read many times. And also, I wanted to ensure that read should not happen till write operation is finished. In other words, I don't want to read MyConfigurationData as NULL.

What I know is the static constructor is called only once in an AppDomain. But, while I am preparing the configuration data, if any thread tries to read this data how would I ensure synchronization effectivey? In the end, I wanted to improve the performance of my read operation.

Can I implement my objective in a lock-free manner?

Daniel Hilgarth
  • 171,043
  • 40
  • 335
  • 443
Imran
  • 694
  • 5
  • 12

3 Answers3

4

From MSDN:

A static constructor is used to initialize any static data, or to perform a particular action that needs performed once only. It is called automatically before the first instance is created or any static members are referenced.

So you don't need to use lock in your code, it is actually thread-safe. Your static constructor is called before MyMethodWhichNeedsConfigurationData is referenced.

public class MyClass
{
   private static MyConfigurationData = null;
   private static MyClass()
   {
   }

   public static MyMethodWhichNeedsConfigurationData()
   {
   }
}
cuongle
  • 74,024
  • 28
  • 151
  • 206
2

As long as you are only ever reading the data, it should already be thread-safe. Very few data-structures are not thread-safe when just reading (the obvious counter-examples might include lazy loading). Note that the static constructor is automatically synchronized by the runtime, so you don't need to concern yourself with multiple threads running the "Initialize Configuration Data" step.

So: as long as nothing ever mutates the data, you are already safe. You could also make it harder to get wrong by hiding the data behind an immutable interface, i.e.

public class ConfigurationData {
    // or some similar immutable API...
    public double GetData1(int index) { return data1[index]; }
    public double GetData2(int index) { return data2[index]; }

    private readonly double[] data1, data2;

    public ConfigurationData(double[] data1, double[] data2) {
        this.data1 = data1;
        this.data2 = data2;
    }
}

Then you don't need any locks:

public class MyClass
{
   private static MyConfigurationData;
   private static MyClass()
   {
     //Initialize Configuration Data
       MyConfigurationData = ...
     //This operation is bit slow as it needs to query the DB to retreive configuration data
   }
   public static MyMethodWhichNeedsConfigurationData()
   {          //Multilple threads can call this method
      var config = MyConfigurationData;

   }
}

Note that removing the locks improves parallelism; it doesn't change raw single-threaded performance.

That said: I should advise against static data generally; it makes it very hard to test, and makes it tricky to do things like multi-tenancy if your needs change. It may be more prudent to have a single configuration instance, but pass it into the system as some form of context. Either approach can be used successfully, though - this is just something to be aware of.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • +1. @Imran, please note that the resulting object is not necessary "lock-free" in traditional sence (using clever code organization to not have lock/Critical sections in the code but at most InterlockedXXXX operations) as runtime most likley uses locks to serialize creation of static objects. – Alexei Levenkov Sep 11 '12 at 08:19
  • @AlexeiLevenkov, I got it. I am OK if the runtime uses locks during creation of static objects. I am only concerned about lock-free read of those static objects. – Imran Sep 11 '12 at 11:00
  • "As long as you are only ever reading the data, it should already be thread-safe." - I think this is not quite true. The initial write may cause the store to be placed into a store buffer, which would lead another core performing a read to read the wrong value from its cache line. You need to use memory barriers to avoid this. –  Sep 11 '12 at 12:23
  • @Blank you could if you really wanted, but I'm pretty sure that is already safe for static constructors and instance constructors - you shouldn't be able to hand out a reference and get empty reads against the fields assigned in the ctor. But you could add an explicit barrier if really worried. – Marc Gravell Sep 12 '12 at 13:29
  • @Marc: it would be safe if the compiler itself wrote a store barrier after init and fetch barriers on thread init. In practise you are extremely likely to be fine, but (as I currently understanding it, but I'm still in the process of understanding this, so *caveat emptor*) it's not guaranteed. –  Sep 12 '12 at 13:51
1

I think you should use Singleton pattern and put your configuration initialization logic in "GetInstance" method which would return the instance of your class.

This way you would not need any locking mechanism for Read.

  • My objective is not related to Singleton pattern as I don't need to create a single instance of MyClass. I want to operate on static configuration data through static methods not instance methods. – Imran Sep 11 '12 at 07:07
  • But you are intended to "I need to create the configuration data only once and use it several multiple times" "private static MyClass()" – Santosh Singh Sep 11 '12 at 07:17