### Устранение гонок данных: использование `TInterlocked.CompareExchange` для создания синглтона в многопоточной среде с интерфейсными объектами в Delphi
Устранение гонок данных: использование TInterlocked.CompareExchange для создания синглтона в многопоточной среде с интерфейсными объектами в Delphi
Гонки данных (race conditions) могут возникать в многопоточных приложениях, когда два или более потоков одновременно обращаются к одному и тому же ресурсу, что может привести к неопределенному поведению программы. Одним из способов устранения гонок данных является использование атомарных операций, таких как TInterlocked.CompareExchange. Этот метод позволяет безопасно обменивать значения переменных в многопоточной среде.
Проблема
В контексте создания синглтона в многопоточной среде, когда объект создается по требованию (lazy create), существует риск, что два потока одновременно попытаются создать объект. Это может привести к гонке данных, если оба потока достигнут проверки на наличие объекта и начнут его создание.
...
var
_FHIRFactory: IFHIRResourceFactory;
function GetFHIRResourceFactory: IFHIRResourceFactory;
begin
if (_FHIRFactory = nil) then
_FHIRFactory := TFHIRResourceFactory.Create;
Result := _FHIRFactory;
end;
Решение с использованием TInterlocked.CompareExchange
В попытке решить проблему гонок данных, пользователь переработал код для использования AtomicCmpExchange, но столкнулся с проблемой, что созданный объект выходил за пределы области видимости:
...
var
_FHIRFactory: IFHIRResourceFactory;
function GetFHIRResourceFactory: IFHIRResourceFactory;
var
newFHIRFactory: IFHIRResourceFactory;
begin
if (_FHIRFactory = nil) then
begin
newFHIRFactory := TFHIRResourceFactory.Create;
if AtomicCmpExchange(Pointer(_FHIRFactory), Pointer(newFHIRFactory), nil) <> nil then
begin
newFHIRFactory := nil; // Объект создан другим потоком, освобождаем ресурсы
end;
// Если не добавить эту строку, объект будет уничтожен, так как выйдет за пределы области видимости
// newFHIRFactory._AddRef
end;
Result := _FHIRFactory;
end;
Подтвержденное решение
К сожалению, TInterlocked не поддерживает работу с интерфейсами, использующими подсчет ссылок. Поэтому управление счетами ссылок необходимо осуществлять вручную.
Пример функции из Indy, которая обрабатывает интерфейсы:
function InterlockedCompareExchangeIntf(var VTarget: IInterface; const AValue, Compare: IInterface): IInterface;
begin
// Необходимо управлять счетами ссылок интерфейсов...
if AValue <> nil then begin
AValue._AddRef;
end;
Result := IInterface(InterlockedCompareExchangePtr(Pointer(VTarget), Pointer(AValue), Pointer(Compare)));
if (AValue <> nil) and (Pointer(Result) <> Pointer(Compare)) then begin
AValue._Release;
end;
end;
Где InterlockedCompareExchangePtr - это обертка для различных платформенно-зависимых функций, включая TInterlocked.CompareExchange при его наличии.
Альтернативный код с использованием TInterlocked.CompareExchange
Используя вышеуказанный подход, можно переписать функцию GetFHIRResourceFactory следующим образом:
function GetFHIRResourceFactory: IFHIRResourceFactory;
var
newFHIRFactory: IFHIRResourceFactory;
begin
if (_FHIRFactory = nil) then
begin
newFHIRFactory := TFHIRResourceFactory.Create;
newFHIRFactory._AddRef;
if TInterlocked.CompareExchange(Pointer(_FHIRFactory), Pointer(newFHIRFactory), nil) <> nil then
begin
newFHIRFactory._Release;
end;
end;
Result := _FHIRFactory;
end;
Если _FHIRFactory устанавливается в новый объект, его счетчик ссылок увеличивается до 2, и затем уменьшается до 1, когда newFHIRFactory выходит из области видимости, оставляя объект живым.
Если _FHIRFactory не устанавливается в новый объект, его счетчик ссылок увеличивается до 2 и затем возвращается к 1, и уменьшается до 0, когда newFHIRFactory выходит из области видимости, что приводит к уничтожению объекта.
Заключение
Использование TInterlocked.CompareExchange позволяет безопасно создавать синглтон в многопоточной среде, но требует внимательного управления счетами ссылок интерфейсов. Следует учитывать, что TInterlocked не работает с интерфейсами напрямую, и необходимо использовать дополнительные операции для управления счетами ссылок.
Контекст: Устранение гонок данных с использованием `TInterlocked.CompareExchange` для создания синглтона в многопоточной среде с интерфейсными объектами в Delphi.
Комментарии и вопросы
Получайте свежие новости и обновления по Object Pascal, Delphi и Lazarus прямо в свой смартфон. Подпишитесь на наш Telegram-канал delphi_kansoftware и будьте в курсе последних тенденций в разработке под Linux, Windows, Android и iOS
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.