C# 线程安全惰性初始化器;正在交换函数<&燃气轮机;好主意?

C# 线程安全惰性初始化器;正在交换函数<&燃气轮机;好主意?,c#,thread-safety,C#,Thread Safety,底部的类是一个快速、线程安全、“无锁”、惰性初始值设定项的实现 我说“无锁”,虽然它不是完全的;它在初始化之前一直使用锁,然后用不使用锁的调用替换获取数据的调用。(类似于带有易失性成员的双重检查锁;从性能测试中,我得到了类似的结果。) 我的问题是,;这是一件好/安全的事情吗 (在我写了这篇文章之后,我注意到.NET4.0有一个LazyInit,它执行相同的操作和更多操作,但在我创建的一个非常精心设计的示例中,我的实现稍微快了一点:-) NB类已修改为包含Value成员和\u get class

底部的类是一个快速、线程安全、“无锁”、惰性初始值设定项的实现

我说“无锁”,虽然它不是完全的;它在初始化之前一直使用锁,然后用不使用锁的调用替换获取数据的调用。(类似于带有易失性成员的双重检查锁;从性能测试中,我得到了类似的结果。)

我的问题是,;这是一件好/安全的事情吗

(在我写了这篇文章之后,我注意到.NET4.0有一个
LazyInit
,它执行相同的操作和更多操作,但在我创建的一个非常精心设计的示例中,我的实现稍微快了一点:-)

NB类已修改为包含
Value
成员和
\u get

class ThreadSafeInitializer<TOutput>
    where TOutput : class
{
    readonly object _sync = new object();
    TOutput _output;
    private volatile Func<TOutput> _get;

    public TOutput Value { get { return _get(); } }

    public ThreadSafeInitializer(Func<TOutput> create)
    {
        _get = () =>
        {
            lock (_sync)
            {
                if (_output == null)
                {
                    _output = create();
                    _get = () => _output; // replace the Get method, so no longer need to lock
                }
                return _output;
            }
        };
    }
}
类线程安全初始值设定项
where TOutput:类
{
只读对象_sync=新对象();
输出输出;
私有易失性函数;
公共TOutput值{get{return _get();}}
公共线程安全初始值设定项(Func create)
{
_获取=()=>
{
锁定(同步)
{
如果(_output==null)
{
_输出=创建();
_get=()=>\u output;//替换get方法,因此不再需要锁定
}
返回输出;
}
};
}
}

使
Get
成员不稳定,以便您确定调用当前版本。据我所知,经过这种修改后,它是安全的。

正如Guffa所说,您希望在Get上添加挥发性物质以使其安全

然而,我发现执行起来有点棘手。我认为这是为了它自己的利益而试图变得有点过于花哨

因此,沿着这些思路的东西更容易遵循:

class Lazy<T> {

    readonly object sync = new object();
    Func<T> init;
    T result;
    volatile bool initialized;

    public Lazy(Func<T> func) {
        this.init = func;
    }

    public T Value {
        get {

            if(initialized) return result;

            lock (sync) {
                if (!initialized) {
                    result = this.init();
                    initialized = true;
                }
            }
            return result;
        }
    }
}
类延迟{
只读对象同步=新对象();
函数初始化;
T结果;
易失性bool初始化;
公共服务(Func Func){
this.init=func;
}
公共价值{
得到{
if(初始化)返回结果;
锁定(同步){
如果(!已初始化){
结果=this.init();
初始化=真;
}
}
返回结果;
}
}
}
次要优势:

  • 不那么花哨
  • 调用Func没有性能影响
  • 仅在初始化时获取锁
  • 通过obj.Value访问值比obj.Get()更直观

    • 我经常写这段代码:

      private SomeThing _myThing = null;
      public SomeThing MyThing 
      { get { return _myThing != null ? _myThing : _myThing = GetSomeThing(); } }
      
      惰性加载很好,可读性很强。我的观点?缺少锁(对象)不是偶然的。延迟加载带有锁的属性会带来麻烦

      应该严格控制lock语句的使用,Paul和Sam都被指控在持有锁时调用了他们没有编写的代码。也许这没什么大不了的,也许它会让你的程序死机。你从一个看起来无辜的财产里做这件事可以节省几毫秒


      我的猜测是,如果您可以安全地加载它两次,而不会产生灾难性的结果,那就更好了。然后在两个线程同时访问同一属性的罕见情况下,加载两个实例。。。取决于加载的内容,如果这真的是件坏事,我打赌大多数情况下,每个线程获得不同的实例并不重要。如果这真的有关系,我建议在构建时执行所需的锁,而不要担心延迟加载。很可能,由于构造函数只能发生在一个线程上,所以您根本不需要锁。

      顺便说一句,在.NET 4.0中包含了一个
      惰性
      结构,但是,随着功能世界的成熟,它真的是那么“花哨”吗?还是只是与人们习惯的有点不同?(从性能角度来看,您的性能更差[在我精心设计的示例中])+1的可读性,但请参见下面我对整个想法的回应。此代码模拟LazyInitMode.EnsureSingleExecution,这将在V4.0的框架中发布,如果您想确保死锁永远不会发生,可以引入防御超时,但请记住,框架版本没有内置超时。是否需要将其设置为易失性?无论是替换的Get,还是旧的Get(它只是锁定了)。随时间推移,让它刷新任何缓存…?对于非易失性字段,重新排序指令的优化技术可能会导致多线程程序中意外和不可预测的结果,这些程序在访问字段时不进行同步,如lock语句(第8.12节)所提供的,但是编译器的选择是一个或另一个,调用哪一个并不重要,因此可以随意对语句重新排序。但是,在我精心设计的例子中,它根本不会改变性能。+1,一个非常聪明的例子,说明了什么是不应该做的;)我觉得这有点苛刻;虽然正如我承认的那样,从锁中调用未知代码是一件坏事,但正如Sam在下面指出的那样,这是.net 4.0框架附带的功能…如果您想模拟框架(v4.0)中附带的LazyInitMode.EnsureSingleExecution,锁是不可避免的。如果你对AllowMultipleExecution感到满意,那就去做吧。这不是为了节省时间,如果同时调用某些初始化代码,可能会导致更大的问题如果它初始化为null呢?哈哈,是的,我以前有过null返回get me。我不明白为什么它一直调用load方法。当我发现我在加载方法中修复了错误时: