【问题标题】:How should "Double-Checked Locking" be implemented in Delphi?Delphi中应该如何实现“双重检查锁定”?
【发布时间】:2010-12-17 21:56:05
【问题描述】:

在 C# 中,以下代码(来自 this 页面)可用于以线程安全的方式延迟实例化单例类:

  class Foo {
        private volatile Helper helper = null;
        public Helper getHelper() {
            if (helper == null) {
                lock(this) {
                    if (helper == null)
                        helper = new Helper();
                }
            }
            return helper;
        }
    }

什么是等效的线程安全 Delphi 代码?


文章还提到了Java中双重检查锁定的两个问题:

  • 有可能在帮助器引用指向新创建的对象之前构造了新对象,这意味着创建了两个对象
  • 可能会在对象仍在创建时将辅助引用指向内存块,这意味着将返回对不完整对象的引用

因此,虽然上述文章中的 C# 和 Java 版本的代码看起来几乎相同,但只有 C# 版本按预期工作。如果这两个问题也存在于 Delphi 版本的双重检查锁定中,这会导致额外的问题?

【问题讨论】:

    标签: delphi design-patterns singleton thread-safety double-checked-locking


    【解决方案1】:

    使用 System.TMonitor 以线程安全的方式锁定对象实例。

    function TFoo.GetHelper(): THelper;
    begin
      if not Assigned(FHelper) then
      begin
        System.MonitorEnter(Self);
        try
          if not Assigned(FHelper) then
            FHelper := THelper.Create();
        finally
          System.MonitorExit(Self);
        end;
      end;
      Result := FHelper;
    end;
    

    更多参考请查看Allen Bauer 中的Lock my object..., please!。事实上,代表。我认为应该去找艾伦。

    【讨论】:

    • 我自己说得再好不过了 ;-)
    • 我感觉这段代码中缺少一些重要的东西:C# 实现使用 volatile 关键字作为私有 Helper 变量。我猜 FHelper 必须声明为threadvar?
    • threadvar 将导致每个线程一个,而不是所有线程共享一个单例。易失性可能是由于 .net 内存模型。但是上面的代码在 x86 上是正确的。
    • @Allen:感谢您的评论,这让我很开心!! :)
    • @mjn:我不是 .NET 专家,C# volatile reference 很明显这里不需要 threadvar,正如@David Heffernan 所说。恕我直言,使用 Delphi 不需要这样的关键字,因为 “最新值” 始终存在于该字段中,显然使用同步对象(如监视器、关键)保护多线程内存访问部分,TMultiReadExclusiveWriteSynchronizer 等。
    【解决方案2】:

    当然,永远值得记住Double-Checked Locking is Broken。事实证明,这个问题不适用于 x86 内存模型,但在未来始终值得牢记。我确信在某个时候会有 Delphi 版本在内存模型受此问题影响的平台上运行。

    Embarcadero 已经开始使用这种模式的无锁版本,带有互锁的比较/交换。例如:

    class function TEncoding.GetUnicode: TEncoding;
    var
      LEncoding: TEncoding;
    begin
      if FUnicodeEncoding = nil then
      begin
        LEncoding := TUnicodeEncoding.Create;
        if InterlockedCompareExchangePointer(Pointer(FUnicodeEncoding), LEncoding, nil) <> nil then
          LEncoding.Free;
      end;
      Result := FUnicodeEncoding;
    end;
    

    我意识到这不是问题的答案,但它并不适合评论!

    【讨论】:

    • 双重检查锁定是否总是坏了?提到的文章说上面的 C# 示例“按预期工作”。
    • 这取决于使用的内存模型。它适用于 x86,但我不确定 x64。它在有自己的内存模型的 Java 上被破坏了。不过这很复杂。
    • x86 和 x64 使用相同的“强”内存模型,但根据 igoro.com/archive/volatile-keyword-in-c-memory-model-explained,Itanium 略有不同
    • 我不确定.net,我感觉内存模型改变了几个版本
    • Rergarding Java:您的答案中链接的文章还说“从 JDK5 开始,有一个新的 Java 内存模型和线程规范。” ...“通过此更改,可以通过将辅助字段声明为 volatile 来使双重检查锁定习语起作用。”
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-08
    • 1970-01-01
    相关资源
    最近更新 更多