【问题标题】:Aquire Singleton class Instance Multithread获取 Singleton 类 Instance Multi Thread
【发布时间】:2013-05-17 11:48:57
【问题描述】:

要获得具有单例模式的类的实例,我想使用以下函数:

这是草图

interface

uses SyncObjs;

type
  TMCriticalSection = class(TCriticalSection)
  private
    Dummy : array [0..95] of Byte;
  end;

var
  InstanceNumber : Integer;
  AObject: TObject;
  CriticalSection: TMCriticalSection;

function getInstance: TObject;

implementation

uses Windows;

function getInstance: TObject;
begin
   //I Want somehow use InterlockedCompareExchange instead of CriticalSession, for example

   if InterlockedCompareExchange(InstanceNumber, 1, 0) > 0 then
   begin
     Result := AObject;
   end
   else
   begin
      CriticalSection.Enter;
      try
        AObject := TObject.Create;
      finally
        CriticalSection.Leave;
      end;
      InterlockedIncrement(InstanceNumber);
      Result := AObject
   end;
end;

initialization
  CriticalSection := TMCriticalSection.Create;
  InstanceNumber := 0;

finalization;
  CriticalSection.Free;

end.

三个问题:

1- 这种设计是线程安全的吗?尤其是带有 InterlockedExchange 的部分。
2- 如何使用 InterlockedCompareExchange?有可能做我正在尝试的事情吗?
3- 这种设计是否比包含关键部分范围内的所有代码更好?

备注: 我的对象是线程安全的,只有我需要序列化的构造!
这不是内部代码,只是重要的部分,我的意思是 getInstance 函数。

编辑

需要使用某种单例对象。
如果 InstanceNumber 的值为零,有什么方法可以使用 InterlockedCompareExchange 进行比较?

1 - 仅当为 0 时才创建对象,否则返回实例。
2 - 当值为 0 时:进入临界区。创建对象。离开临界区。
3 - 这样做会更好,而不是涉及关键部分范围内的所有代码?

【问题讨论】:

    标签: multithreading delphi synchronization singleton delphi-6


    【解决方案1】:

    有一种称为"Lock-free initialization" 的技术可以满足您的需求:

    interface
    
    function getInstance: TObject;
    
    implementation
    
    var
       AObject: TObject;
    
    function getInstance: TObject;
    var
       newObject: TObject;
    begin
       if (AObject = nil) then
       begin
          //The object doesn't exist yet. Create one.
          newObject := TObject.Create;
    
          //It's possible another thread also created one.
          //Only one of us will be able to set the AObject singleton variable
          if InterlockedCompareExchangePointer(AObject, newObject, nil) <> nil then
          begin
             //The other beat us. Destroy our newly created object and use theirs.
             newObject.Free;
          end;
       end;
    
       Result := AObject;
    end;
    

    InterlockedCompareExchangePointer 的使用在操作周围建立了一个完整的内存屏障。使用 release 语义 (以确保对象的构造在执行比较交换之前完成)可能能够摆脱InterlockedCompareExchangeRelease。问题在于:

    • 我不够聪明,无法知道单独使用 Release 语义是否真的会起作用(只是导致一个比我说的更聪明的人,并不意味着我知道他在说什么)李>
    • 您正在构建一个对象,内存屏障性能问题是您最不担心的(这是线程安全)

    注意:任何发布到公共领域的代码。无需署名。

    【讨论】:

    【解决方案2】:

    您的设计不起作用,即使不了解 InterlockedCompareExchange 的作用,也可以看出这一点。事实上,不管InterlockedCompareExchange是什么意思,你的代码都坏了。

    要看到这一点,请考虑两个线程同时到达getInstance 中的if 语句。让我们考虑他们采取哪些分支的三个选项:

    1. 他们都选择了第二个分支。然后您创建两个实例,您的代码不再实现单例。
    2. 他们都选择了第一个分支。那么你永远不会创建实例。
    3. 一个选择第一个,另一个选择第二个。但是由于第一个分支中没有锁,因此采用该路由的线程可以在其他线程写入之前读取AObject

    如果我必须实现你的getInstance 函数,我个人会使用double-checked locking

    【讨论】:

    • 是的,你是对的,我对使用 InterlockedCompare 感到非常兴奋,以至于我什至没有意识到这部分。谢谢
    • 最好的办法就是把所有代码都放在临界区上下文中,对吗?
    • 您可以使用双重检查锁定,这在 x86/x64 上的 Delphi 中有效。我在我的答案中添加了一个链接到另一个答案(我的一个),其中更详细地介绍了这个主题。
    • 阅读我链接到的答案。本质上,x86 和 x64 内存模型很强大,并且 Delphi 没有优化从全局变量到寄存器的读/写。所以,DCL 在 x86/x64 上的 Delphi 中工作
    猜你喜欢
    • 1970-01-01
    • 2013-06-27
    • 2012-10-07
    • 2013-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多