Присваивает ли Delphi переменную экземпляра до того, как объект будет полностью построен?
Другими словами, учитывая переменную:
var
customer: TCustomer = nil;
мы затем создаем клиента и присваиваем его переменной:
customer := TCustomer.Create;
Возможно ли, что клиент
не может быть 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;
и переменной присваивается значение до того, как происходит построение. Это заставляет поток предположить, что клиент
является допустимым объектом просто потому, что переменная назначена.
Может ли эта многопоточная одноэлементная ошибка возникать в Delphi (5)?
Дополнительный вопрос
Существует ли общепринятый, потокобезопасный шаблон проектирования однократной инициализациидля Delphi? Многие реализовали синглтоныв Delphi, переопределив NewInstance
и FreeInstance
; их реализации потерпят неудачу в нескольких потоках.
Строго говоря, мне нужен не ответ о том, как реализовать и singleton, а lazy-initialization. В то время как синглтонымогут использоватьленивую инициализацию, ленивая инициализацияне ограничивается одиночками.
Обновление
Два человека предложили ответ , который содержит распространенную ошибку. сломанный алгоритм блокировки с двойной проверкой, переведенный в 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;
Обновление
Я создал некоторый код и посмотрел на окно процессора. Кажется, что этот компилятор с моими настройками оптимизации в этой версии Windows с этим объектом сначала создает объект, , затем присваивает переменную:
customer := TCustomer.Create;
mov dl,$01
mov eax,[$0059d704]
call TCustomer.Create
mov [customer],eax;
Result := customer;
mov eax,[customer];
Конечно, я не могу сказать, что это гарантированно всегда работает сюда.