【问题标题】:Is there a way to implement a lock free static configuration data?有没有办法实现无锁静态配置数据?
【发布时间】:2012-09-11 06:52:27
【问题描述】:
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
      }
   }
}

在我的应用程序中,我只需要创建一次配置数据并多次使用它。换句话说,我写一次,读很多次。而且,我想确保在写操作完成之前不会发生读取。换句话说,我不想将 MyConfigurationData 读取为 NULL。

我知道静态构造函数在 AppDomain 中只调用一次。但是,当我准备配置数据时,如果任何线程试图读取这些数据,我将如何确保同步有效?最后,我想提高读取操作的性能。

我可以以无锁的方式实现我的目标吗?

【问题讨论】:

    标签: c# lock-free static-content


    【解决方案1】:

    来自MSDN

    静态构造函数用于初始化任何静态数据,或执行只需要执行一次的特定操作。在创建第一个实例或引用任何静态成员之前自动调用它。

    所以你不需要在你的代码中使用lock,它实际上是线程安全的。在引用 MyMethodWhichNeedsConfigurationData 之前调用您的静态构造函数。

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

    【讨论】:

    • 谢谢,我想仔细检查一下我对静态构造函数的看法是否正确。
    【解决方案2】:

    只要您只是读取数据,它就应该是线程安全的。很少有数据结构在只是读取时不是线程安全的(明显的反例可能包括延迟加载)。请注意,静态构造函数由运行时自动同步,因此您无需担心运行“初始化配置数据”步骤的多个线程。

    所以:只要数据没有发生任何变异,您就已经安全了。您还可以通过将数据隐藏在不可变接口后面来使其更难出错,即

    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;
        }
    }
    

    那么你就不需要任何锁了:

    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;
    
       }
    }
    

    请注意,移除锁可以提高并行性;它不会改变原始单线程性能。

    也就是说:我应该建议不要使用静态数据一般;它使测试变得非常困难,并且如果您的需求发生变化,则很难执行多租户之类的事情。 拥有单个配置实例可能更谨慎,但将其作为某种形式的上下文传递到系统中。不过,这两种方法都可以成功使用——这只是需要注意的一点。

    【讨论】:

    • 感谢您的解释。
    • +1。 @Imran,请注意,生成的对象在传统意义上不需要“无锁”(使用巧妙的代码组织在代码中没有锁/关键部分,但最多 InterlockedXXXX 操作)作为运行时大多数likley使用锁来序列化创建静态对象。
    • @AlexeiLevenkov,我明白了。如果运行时在创建静态对象期间使用锁,我可以。我只关心那些静态对象的无锁读取。
    • "只要你只是读取数据,它就应该是线程安全的。" - 我认为这并不完全正确。初始写入可能会导致存储被放置到存储缓冲区中,这将导致另一个内核执行读取以从其高速缓存行中读取错误的值。您需要使用内存屏障来避免这种情况。
    • @Blank 如果你真的想要,你可以,但我很确定这对于静态构造函数和实例构造函数来说已经是安全的了——你不应该分发一个引用并得到空的读取在ctor中分配的字段。但如果你真的很担心,你可以添加一个明确的障碍。
    【解决方案3】:

    我认为您应该使用单例模式并将您的配置初始化逻辑放在“GetInstance”方法中,该方法将返回您的类的实例。

    这样您就不需要任何读取锁定机制。

    【讨论】:

    • 我的目标与单例模式无关,因为我不需要创建 MyClass 的单个实例。我想通过静态方法而不是实例方法对静态配置数据进行操作。
    • 但你的意图是“我只需要创建一次配置数据并多次使用它”“private static MyClass()”
    猜你喜欢
    • 1970-01-01
    • 2011-02-10
    • 2010-10-09
    • 2017-12-17
    • 1970-01-01
    • 1970-01-01
    • 2020-03-18
    • 2023-03-22
    相关资源
    最近更新 更多