Delphi在构造对象之前分配变量? [英] Does Delphi assign the variable before the object is constructed?

查看:123
本文介绍了Delphi在构造对象之前分配变量?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述



换句话说,给定一个变量:

  var 
customer:TCustomer = nil;

然后我们构造一个客户并将其分配给变量:

  customer:= TCustomer.Create; 

有可能客户不能 nil ,但不能指向完全构建的 TCustomer



< hr>

执行延迟初始化时,这成为一个问题:

  function SacrifialCustomer:TCustomer ; 
begin
if(customer = nil)then
begin
criticalSection.Enter;
try
customer:= TCustomer.Create;
finally
criticalSection.Leave;
结束
结束
结果:= customer;
结束

错误在下列行中:

  if(customer = nil)

线程调用:

  customer:= TCustomer.Create; 

并且在构造之前赋予变量值。这导致线程假定 客户是一个有效的对象,因为该变量被分配。


$ b $这个多线程单例错误可以在Delphi(5)中发生吗?






奖金问题



是否有接受,线程安全的一次初始化 Delphi的设计模式?许多人通过覆盖 NewInstance FreeInstance ,在Delphi中实现了他们的实现将在多线程中失败。



严格来说,我不是在如何实现和单例的答案之后,而是懒惰初始化单身人士可以使用懒惰初始化,延迟初始化不限于单身人士。



更新



两个人建议一个答案,其中包含一个常见的错误。将断开的双重锁定算法转换为Delphi

  //破碎的多线程
//双重锁定成语
如果(customer = nil)然后
begin
criticalSection.Enter;
try
if(customer = nil)then
customer:= TCustomer.Create;
finally
criticalSection.Leave;
结束
结束
结果:= customer;

维基百科


直观地,这个算法似乎是一个有效的解决方案。然而,这种技术有许多微妙的问题,通常应该避免。







另一个建议:

  function SacrificialCustomer:TCustomer; 
var
tempCustomer:TCustomer;
begin
tempCustomer = customer;
if(tempCustomer = nil)then
begin
criticalSection.Enter;
try
if(customer = nil)then
begin
tempCustomer:= TCustomer.Create;
customer:= tempCustomer;
结束
finally
criticalSection.Leave;
结束
结束
结果:= customer;
结束

更新



我创建了一些代码,并查看了cpu窗口。似乎这个编译器,在我的优化设置下,在这个版本的Windows上,用这个对象,首先构造对象,然后分配变量:

  customer:= TCustomer.Create; 
mov dl,$ 01
mov eax,[$ 0059d704]
调用TCustomer.Create
mov [customer],eax;
结果:= customer;
mov eax,[customer];

当然我不能说保证总是这样工作。

解决方案

我的阅读你的问题是你问这个问题:


我如何使用Delphi 5定位x86硬件,实现单线程的线程安全的延迟初始化。


知识你有三个选择。



1。使用锁

 函数GetCustomer:TCustomer; 
begin
Lock.Acquire;
尝试
如果没有分配(客户)然后//客户是一个全局变量
客户:= TCustomer.Create;
结果:=客户;
finally
Lock.Release;
结束
结束

这样做的缺点是如果在 GetCustomer 然后锁定的序列化将禁止缩放。我怀疑人们担心的不仅仅是必要的。例如,如果您有一个执行大量工作的线程,该线程可以将单引号的本地副本作为参考,以减少争用。

  procedure ThreadProc; 
var
MyCustomer:TCustomer;
begin
MyCustomer:= GetCustomer;
//用MyCustomer
结束做很多工作

2。双重锁定



这是一种技术,一旦创建单例,就可以避免锁争用。

 函数GetCustomer:TCustomer; 
开始
如果分配(客户)然后
开始
结果:=客户;
退出;
结束

Lock.Acquire;
尝试
如果没有分配(客户)然后
客户:= TCustomer.Create;
结果:=客户;
finally
Lock.Release;
结束
结束

双重锁定是一种具有相当方格的历史的技术。最着名的讨论是双重检查的锁定被破坏声明。这主要是在Java的上下文中,所描述的问题不适用于您的情况(Delphi编译器,x86硬件)。事实上,对于Java,随着JDK5的到来,我们现在可以说双重锁定是固定的。



Delphi编译器不会重新命令写入关于对象的构造的单例变量。此外,强大的x86内存模式意味着处理器重新排序不会破坏这一点。请参阅谁在x86上订购记忆栅栏?



简单来说,双重锁定在Delphi x86上没有损坏。此外,x64内存模式也很强大,双重锁定也不会被破坏。



3。比较和交换



如果您不介意创建单个类的多个实例,然后丢弃除一个以外的所有实例,您可以使用比较和交换。 VCL的最新版本使用了这种技术。看起来像这样:

 函数GetCustomer; 
var
LCustomer:TCustomer;
begin
如果没有分配(客户)然后
begin
LCustomer:= TCustomer.Create;
如果InterlockedCompareExchangePointer(指针(客户),LCustomer,nil)<>零,然后
LCustomer.Free;
结束
结果:=客户;
结束


Does Delphi assign an instance variable before the object is fully constructed?

In other words, given a variable:

var
   customer: TCustomer = nil; 

we then construct a customer and assign it to the variable:

customer := TCustomer.Create;

Is it possible that customer can be not nil, but not point to a fully constructed TCustomer?


This becomes a problem when performing lazy initialization:

function SacrifialCustomer: TCustomer;
begin
   if (customer = nil) then
   begin
      criticalSection.Enter;
      try
         customer := TCustomer.Create;
      finally 
         criticalSection.Leave;
      end;
   end;
   Result := customer;
end;

The bug is in the line:

if (customer = nil) 

It is possible that another thread calls:

customer := TCustomer.Create;

and the variable is assigned a value before construction happens. This causes the thread to assume that customer is a valid object simply because the variable is assigned.

Can this multi-threaded singleton bug happen in Delphi (5)?


Bonus Question

Is there an accepted, thread-safe, one-time initialization design pattern for Delphi? Many people have implemented singletons in Delphi by overriding NewInstance and FreeInstance; their implementations will fail in multiple threads.

Strictly speaking i'm not after an answer on how to implement and singleton, but lazy-initialization. While singletons can use lazy-initialization, lazy initialization is not limited to singletons.

Update

Two people suggested an answer that contains a common mistake. The broken double-checked locking algorithm translated to Delphi:

// Broken multithreaded version
// "Double-Checked Locking" idiom
if (customer = nil) then
begin
   criticalSection.Enter;
   try
      if (customer = nil) then
         customer := TCustomer.Create;
   finally
      criticalSection.Leave;
   end;
end;
Result := customer;

From Wikipedia:

Intuitively, this algorithm seems like an efficient solution to the problem. However, this technique has many subtle problems and should usually be avoided.


Another buggy suggestion:

function SacrificialCustomer: TCustomer;
var
  tempCustomer: TCustomer;
begin
   tempCustomer = customer;
   if (tempCustomer = nil) then
   begin
      criticalSection.Enter;
      try
         if (customer = nil) then
         begin
            tempCustomer := TCustomer.Create;
            customer := tempCustomer;
         end;
      finally
         criticalSection.Leave;
      end;
   end;
   Result := customer;
end;

Update

i created some code and looked at the cpu window. It seems that this compiler, with my optimization settings, on this version of Windows, with this object, constructs the object first, then assigns the variable:

customer := TCustomer.Create;
       mov dl,$01
       mov eax,[$0059d704]
       call TCustomer.Create
       mov [customer],eax;
Result := customer;
       mov eax,[customer];

Of course i cannot say that's guaranteed to always work that way.

解决方案

My reading of your question is that you are asking this:

How can I, using Delphi 5 targeting x86 hardware, implement thread-safe lazy initialization of a singleton.

To the best of my knowledge you have three options.

1. Use a lock

function GetCustomer: TCustomer;
begin
  Lock.Acquire;
  try
    if not Assigned(Customer) then // Customer is a global variable
      Customer := TCustomer.Create;
    Result := Customer;
  finally 
    Lock.Release;
  end;
end;

The downside of this is that if there is contention on GetCustomer then the serialization of the lock will inhibit scaling. I suspect that people worry about that a lot more than is necessary. For example, if you have a thread that performs a lot of work, that thread can take a local copy of the reference to the singleton to reduce the contention.

procedure ThreadProc;
var
  MyCustomer: TCustomer;
begin
  MyCustomer := GetCustomer;
  // do lots of work with MyCustomer
end;

2. Double checked locking

This is a technique that allows you, once the singleton has been created, to avoid the lock contention.

function GetCustomer: TCustomer;
begin
  if Assigned(Customer) then
  begin
    Result := Customer;
    exit;
  end;

  Lock.Acquire;
  try
    if not Assigned(Customer) then
      Customer := TCustomer.Create;
    Result := Customer;
  finally 
    Lock.Release;
  end;
end;

Double checked locking is a technique with a rather chequered history. The most famous discussion is The "Double-Checked Locking is Broken" Declaration. This is set mostly in the context of Java and the problems described do not apply to your situation (Delphi compiler, x86 hardware). Indeed, for Java, with the advent of JDK5, we can now say that Double-Checked Locking is Fixed.

The Delphi compiler doesn't re-order the write to the singleton variable with respect to the construction of the object. What's more, the strong x86 memory model means that processor re-orderings don't break this. See Who ordered memory fences on an x86?

Simply put, double checked locking is not broken on Delphi x86. What's more, the x64 memory model is also strong and double checked locking is not broken there either.

3. Compare and swap

If you don't mind the possibility of creating multiple instances of the singleton class, and then discarding all but one, you can use compare and swap. Recent versions of the VCL make use of this technique. It looks like this:

function GetCustomer;
var
  LCustomer: TCustomer;
begin
  if not Assigned(Customer) then 
  begin
    LCustomer := TCustomer.Create;
    if InterlockedCompareExchangePointer(Pointer(Customer), LCustomer, nil) <> nil then
      LCustomer.Free;
  end;
  Result := Customer;
end;

这篇关于Delphi在构造对象之前分配变量?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆