Подскажите пожалуйста, при такой реализации списка, может быть проблема при его инициализации из нескольких потоков?

Стараюсь программировать код на основе элегантных обьектов (Девид Вест, Егор Бугаенко) в 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 шт):

Автор решения: Vlad Chapl

если компоненты не помечены как thread safe, то программист сам должен позаботится о безопасном многопоточном использовании. В рамках "как например критическая секция поможет" примерно так:

  1. заходим в критическую секцию
  2. выполняем проверку "инициализация была?", если нет, то делаем инициализацию.
  3. выходим из критической секции.

Тут важно, что проверку и действие мы выполняем внутри безопасной секции, не боимся, что два потока одновременно захотят сделать одно и то же или как-то помешают.

Так же можно (нужно) знать про мютексы, семафоры (и т.п.)

→ Ссылка