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;
并且变量在构造发生之前被赋值。这导致线程仅仅因为分配了变量就假定它是一个有效的对象。customer
Delphi(5)中会出现这种多线程单例的bug吗?
奖金问题
Delphi是否有一种公认的、线程安全的、一次性的初始化设计模式?许多人在 Delphi 中通过覆盖和实现单例;他们的实现将在多个线程中失败。NewInstance
FreeInstance
严格来说,我不是在回答如何实现和单例,而是在惰性初始化。虽然单例可以使用延迟初始化,但延迟初始化并不限于单例。
更新
两个人提出了一个包含常见错误的答案。损坏的双重检查锁定算法转换为 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;
来自维基百科:
直观地说,这个算法似乎是解决问题的有效方法。然而,这种技术有许多微妙的问题,通常应该避免。
另一个错误的建议:
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];
当然,我不能说保证总是这样工作。