O Delphi atribui a variável antes que o objeto seja construído?
O Delphi atribui uma variável de instância antes que o objeto seja totalmente construído?
Em outras palavras, dada uma variável:
var
customer: TCustomer = nil;
Em seguida, construímos um cliente e o atribuímos à variável:
customer := TCustomer.Create;
É possível quecustomer
pode não sernil
, mas não aponta para um totalmente construídoTCustomer
?
Isso se torna um problema ao executar a inicialização lenta:
function SacrifialCustomer: TCustomer;
begin
if (customer = nil) then
begin
criticalSection.Enter;
try
customer := TCustomer.Create;
finally
criticalSection.Leave;
end;
end;
Result := customer;
end;
O bug está na linha:
if (customer = nil)
É possível que outro encadeamento chame:
customer := TCustomer.Create;
e a variável recebe um valor antes que a construção aconteça. Isso faz com que o encadeamentoassumir naquelacustomer
é um objeto válido simplesmente porque a variável é atribuída.
Esse bug singleton multi-thread pode acontecer em Delphi (5)?
Pergunta bônus
Existe um aceito, thread-safe,inicialização única padrão de design para Delphi? Muitas pessoas implementaramsingletons em Delphi, substituindoNewInstance
eFreeInstance
; suas implementações falharão em vários segmentos.
Estritamente falando eu não estou depois de uma resposta sobre como implementar esingleton, masinicialização lenta. Enquantosingletons possousar inicialização lenta,inicialização lenta não está limitado a singletons.
Atualizar
Duas pessoas sugeriram uma respostaque contém um erro comum. oalgoritmo de travamento duplo verificado traduzido para 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;
DeWikipedia:
Intuitivamente, esse algoritmo parece ser uma solução eficiente para o problema. No entanto, esta técnica tem muitos problemas sutis e geralmente deve ser evitada.
Outra sugestão de buggy:
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;
Atualizar
Eu criei algum código e olhei para a janela da cpu. Parece que este compilador, com minhas configurações de otimização, nesta versão do Windows, com este objeto, constrói o objeto primeiro,então atribui a variável:
customer := TCustomer.Create;
mov dl,$01
mov eax,[$0059d704]
call TCustomer.Create
mov [customer],eax;
Result := customer;
mov eax,[customer];
É claro que não posso dizer que é garantido que sempre funcione dessa maneira.