gpt4 book ai didi

delphi - Delphi 是否在构造对象之前分配变量?

转载 作者:行者123 更新时间:2023-12-03 15:03:27 24 4
gpt4 key购买 nike

Delphi 是否在对象完全构造之前分配实例变量?

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

var
customer: TCustomer = nil;

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

customer := TCustomer.Create;

是否有可能customer不能为nil,但不能指向完全构造的TCustomer

<小时/>

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

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

错误所在行:

if (customer = nil) 

有可能另一个线程调用:

customer := TCustomer.Create;

and the variable is assigned a value before construction happens 。这会导致线程假设 customer 是一个有效对象,因为变量已被分配。

这个多线程单例错误会在 Delphi (5) 中发生吗?

<小时/>

奖励问题

是否有一个可接受的、线程安全的one-time initialization Delphi 的设计模式?许多人通过重写NewInstanceFreeInstance在Delphi中实现了单例;它们的实现将在多个线程中失败。

严格来说,我并不是在寻找如何实现和单例的答案,而是延迟初始化。虽然单例可以使用延迟初始化,但延迟初始化并不限于单例。

更新

两个人建议了一个答案 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;

来自Wikipedia :

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

<小时/>

另一个错误建议:

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;

更新

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

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

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

最佳答案

我对你的问题的解读是你在问这个:

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

据我所知,您有三种选择。

<强>1。使用锁

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;

这样做的缺点是,如果 GetCustomer 上存在争用,那么锁的序列化将抑制扩展。我怀疑人们对此的担心超出了必要的范围。例如,如果您有一个执行大量工作的线程,则该线程可以获取对单例的引用的本地副本以减少争用。

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

<强>2。双重检查锁定

这种技术允许您在创建单例后避免锁争用。

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;

双重检查锁定是一种有着相当复杂历史的技术。最著名的讨论是The "Double-Checked Locking is Broken" Declaration 。这主要是在 Java 上下文中设置的,所描述的问题不适用于您的情况(Delphi 编译器、x86 硬件)。确实,对于 Java 来说,随着 JDK5 的出现,我们现在可以说 Double-Checked Locking 已经被修复了。

Delphi 编译器不会根据对象的构造重新排序对单例变量的写入。此外,强大的 x86 内存模型意味着处理器重新排序不会破坏这一点。请参阅Who ordered memory fences on an x86?

简单地说,双重检查锁定在 Delphi x86 上不会被破坏。更重要的是,x64 内存模型也很强大,并且双重检查锁定也没有被破坏。

<强>3。比较和交换

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

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 - Delphi 是否在构造对象之前分配变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10805769/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com