Подскажите пожалуйста, при такой реализации списка, может быть проблема при его инициализации из нескольких потоков?
Стараюсь программировать код на основе элегантных обьектов (Девид Вест, Егор Бугаенко) в Delphi. Следующий код универсального списка прекрасно работает в одном потоке. Если список уже заполнен (уже был вызван метод Init), то как я понимаю все будет прекрасно работать и в многопотоковом режиме. Но я не до конца понимаю что будет если несколько потоков обратяться к еще не инициализированному списку (метод Init еще не вызывался). Проблема может заключаться что несколько потоков смогут произвести инициализацию и принципе где может быть проблема, так это вот тут:
procedure TGenericListConst<T>._Init;
begin
try
if FNeedInit then
begin
FNeedInit := False;
Как вы считаете действительно ли может быть проблема и если да то как например критическая секция поможет ее избежать?
// Интерфейс.
type
// Константный список - такой
// список изнутри может заполнить себя один раз,
// и после этого нет возможности его изменить
// ни снаружи ни изнутри.
IGenericListConst<T> = interface
// Количество строк списка.
function Count: Integer;
// Строка списка.
function Value(const _Indx: Integer): T;
end;
type
// Константный список - такой
// список изнутри может заполнить себя один раз,
// и после этого нет возможности его изменить
// ни снаружи ни изнутри.
TGenericListConst<T> = class(TInterfacedObject, IGenericListConst<T>)
strict private
FList: TList<T>;
FNeedInit: Boolean;
procedure _Init;
strict protected
// Эта процедура единственный способ заполнить
// список, она вызываться единожды и нет способа
// получить доступа к FList из другого места.
//
// Все это дает возможность заполнить список один раз
// и более он не может быть изменен.
procedure Init(_List: TList<T>); virtual; abstract;
public
constructor Create;
destructor Destroy; override;
function Count: Integer;
function Value(const _Indx: Integer): T;
end;
// Реализация.
{ TGenericListConst<T> }
constructor TGenericListConst<T>.Create;
begin
inherited;
FList := nil;
FNeedInit := True;
end;
destructor TGenericListConst<T>.Destroy;
begin
FreeAndNil(FList);
inherited;
end;
procedure TGenericListConst<T>._Init;
begin
try
if FNeedInit then
begin
FNeedInit := False;
FList := TList<T>.Create;
Init(FList);
end;
except
on E: Exception do
raise Exception.Create(ClassName + '._Init: ' + E.Message);
end;
end;
function TGenericListConst<T>.Count: Integer;
begin
_Init;
Result := FList.Count;
end;
function TGenericListConst<T>.Value(const _Indx: Integer): T;
begin
try
_Init;
Result := FList[_Indx];
except
on E: Exception do
raise Exception.Create(ClassName + '.Value: ' + E.Message);
end;
end;
Ответы (1 шт):
если компоненты не помечены как thread safe, то программист сам должен позаботится о безопасном многопоточном использовании. В рамках "как например критическая секция поможет" примерно так:
- заходим в критическую секцию
- выполняем проверку "инициализация была?", если нет, то делаем инициализацию.
- выходим из критической секции.
Тут важно, что проверку и действие мы выполняем внутри безопасной секции, не боимся, что два потока одновременно захотят сделать одно и то же или как-то помешают.
Так же можно (нужно) знать про мютексы, семафоры (и т.п.)