Czy Delphi przypisuje zmienną przed skonstruowaniem obiektu?
Czy Delphi przypisuje zmienną instancji, zanim obiekt zostanie w pełni skonstruowany?
Innymi słowy, podając zmienną:
var
customer: TCustomer = nil;
następnie konstruujemy klienta i przypisujemy go do zmiennej:
customer := TCustomer.Create;
Czy to możliwecustomer
nie może byćnil
, ale nie wskazuje na w pełni skonstruowanyTCustomer
?
Staje się to problemem przy wykonywaniu leniwej inicjalizacji:
function SacrifialCustomer: TCustomer;
begin
if (customer = nil) then
begin
criticalSection.Enter;
try
customer := TCustomer.Create;
finally
criticalSection.Leave;
end;
end;
Result := customer;
end;
Błąd jest w linii:
if (customer = nil)
Możliwe, że inny wątek wywołuje:
customer := TCustomer.Create;
a zmiennej przypisuje się wartość przed rozpoczęciem budowy. Powoduje to wątekzałożyć żecustomer
jest poprawnym obiektem po prostu dlatego, że zmienna jest przypisana.
Czy ten wielowątkowy błąd singletonowy może wystąpić w Delphi (5)?
Pytanie bonusowe
Czy istnieje akceptowany, bezpieczny wątekjednorazowa inicjalizacja wzór dla Delphi? Wiele osób wdrożyłosingletony w Delphi przez nadpisywanieNewInstance
iFreeInstance
; ich implementacje zawiodą w wielu wątkach.
Ściśle mówiąc nie jestem po odpowiedzi na temat sposobu wdrożenia isingel, aleleniwa inicjalizacja. Podczassingletony mogąposługiwać się leniwa inicjalizacja,leniwa inicjalizacja nie ogranicza się do singletonów.
Aktualizacja
Dwie osoby zasugerowały odpowiedźktóry zawiera powszechny błąd. Thezłamany podwójnie sprawdzony algorytm blokowania przetłumaczone na 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;
Intuicyjnie ten algorytm wydaje się skutecznym rozwiązaniem problemu. Jednak ta technika ma wiele subtelnych problemów i zwykle należy jej unikać.
Kolejna sugestia dotycząca błędów:
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;
Aktualizacja
utworzyłem kod i przejrzałem okno procesora. Wydaje się, że ten kompilator, z moimi ustawieniami optymalizacji, w tej wersji systemu Windows, z tym obiektem, najpierw konstruuje obiekt,następnie przypisuje zmienną:
customer := TCustomer.Create;
mov dl,$01
mov eax,[$0059d704]
call TCustomer.Create
mov [customer],eax;
Result := customer;
mov eax,[customer];
Oczywiście nie mogę powiedzieć, że to gwarantuje, że zawsze działa w ten sposób.