解释代码:c#锁定功能和线程

解释代码:c#锁定功能和线程,c#,multithreading,locking,C#,Multithreading,Locking,我在一些项目中使用了这个模式(这段代码是从中剪下来的),我理解它的作用,但我对这个模式的解释非常感兴趣。具体而言: 为什么重复检查\u dependenciesregisted 为什么要使用lock(lock){} 谢谢 public class DependencyRegistrarModule : IHttpModule { private static bool _dependenciesRegistered; private static readonly object

我在一些项目中使用了这个模式(这段代码是从中剪下来的),我理解它的作用,但我对这个模式的解释非常感兴趣。具体而言:

  • 为什么重复检查
    \u dependenciesregisted
  • 为什么要使用
    lock(lock){}
  • 谢谢

    public class DependencyRegistrarModule : IHttpModule
    {
        private static bool _dependenciesRegistered;
        private static readonly object Lock = new object();
    
        public void Init(HttpApplication context)
        {
            context.BeginRequest += context_BeginRequest;
        }
    
        public void Dispose() { }
    
        private static void context_BeginRequest(object sender, EventArgs e)
        {
            EnsureDependenciesRegistered();
        }
    
        private static void EnsureDependenciesRegistered()
        {
            if (!_dependenciesRegistered)
            {
                lock (Lock)
                {
                    if (!_dependenciesRegistered)
                    {
                        new DependencyRegistrar().ConfigureOnStartup();
                        _dependenciesRegistered = true;
                    }
                }
            }
        }
    }
    
    这是最新的

    lock
    语句确保块内的代码不会同时在两个线程上运行。
    由于
    lock
    语句有点昂贵,因此代码会在进入锁之前检查它是否已经初始化。
    但是,因为不同的线程可能在外部检查之后初始化了它,所以它需要在锁内部再次检查


    .

    双重检查是因为两个线程可能同时命中
    EnsureDependencesRegistered
    ,两个线程都发现它未注册,因此都试图获取锁

    锁(lock)
    本质上是互斥的一种形式;只有一个线程可以拥有锁-另一个线程必须等待锁被释放(在
    lock(…){…}
    语句末尾)


    因此在这种情况下,一个线程可能(虽然不太可能)是
    锁中的第二个线程,因此每个线程都必须进行双重检查,以防它是第二个线程,并且工作已经完成。

    这是一个性能问题


    如果工作已经完成,那么初始测试可以让它快速摆脱困境。此时,它会执行可能非常昂贵的锁定,但它必须再次检查,因为另一个线程可能已经注册了它。

    双重检查的锁定模式大致如下:

    您有一个要有条件地执行一次的操作

    if (needsToDoSomething) {
       DoSomething();
       needsToDoSomething = false;
    }
    
    但是,如果您在两个线程上运行,则两个线程都可能在将标志设置为false之前检查标志并执行操作。因此,您添加了一个锁

    lock (Lock) {
        if (needsToDoSomething) {
           DoSomething();
           needsToDoSomething = false;
        }
    }
    
    然而,每次运行这段代码时获取锁可能会很慢,因此您决定,让我们只在实际需要时尝试获取锁

     if (needsToDoSomething)
        lock (Lock) {
            if (needsToDoSomething) {
               DoSomething();
               needsToDoSomething = false;
            }
        }
    

    您无法删除内部检查,因为您再次遇到一个问题,即在锁之外执行的任何检查都可能在两个不同线程上两次为真。

    该锁阻止两个线程运行ConfigureOnStartup()。在if(!\u dependenciesRegistered)和ConfigureOnStartup()设置\u dependenciesRegistered=true的点之间,另一个线程可以检查它是否已注册。换言之:

  • 线程1:_dependenciesRegistered==false
  • 线程2:_dependenciesRegistered==false
  • 线程1:ConfigureOnStartup()/_dependenciesRegistered=true
  • 线程2:没有“看到”它已经注册,所以再次运行ConfigureOnStartup()

  • +1特别是wikipedia文章中的“这不是最好的方法”注释:当在某些语言/硬件组合中实现该模式时,它可能是不安全的。这不是实现单例的最佳方法,因为很可能会出错。我认为这个实现是有缺陷的,因为它缺少变量上的volatile。在某些情况下,正确实现此模式可能是更好的选择。“这不是最好的方式”链接实际上似乎并不适用,因为它反对此模式的理由是“不能保证在设置变量之前完成单例构造函数”。但是,这里没有将变量设置为构造函数,因此,双重检查锁工作正常,即使没有
    volatile
    @BlueRaja:这种方式仍然比静态构造函数慢。