Delphi - Обновление глобальной строки из второго потока - PullRequest
9 голосов
/ 15 октября 2011

Я экспериментирую с многопоточностью в Delphi (XE) и столкнулся с проблемой использования глобальной переменной между основным потоком VCL и вторым рабочим потоком.

Мой проект включает в себя 2-й рабочий поток, который просматривает некоторые файлы и обновляет строку globalvar, используя текущее имя файла. Этот globalvar затем выбирается с помощью таймера в главном потоке VCL и обновляет строку состояния.

Однако я заметил, что иногда возникает «недопустимая операция с указателем» ... или «Недостаточно памяти», или рабочий поток просто перестает отвечать (вероятно, тупик).

Поэтому я создал тестовое приложение, чтобы идентифицировать и значительно увеличить вероятность ошибки, чтобы я мог видеть, что происходит.

type
  TSyncThread = class(TThread)
  protected
    procedure Execute; override;
end;

var
  Form11: TForm11;
  ProgressString : String;
  ProgressCount : Int64;
  SyncThread : TSyncThread;
  CritSect : TRTLCriticalSection;

implementation

{$R *.dfm}

procedure TForm11.StartButtonClick(Sender: TObject);
begin
  Timer1.Enabled := true;
  SyncThread := TSyncThread.Create(True);
  SyncThread.Start;
end;

procedure TForm11.StopbuttonClick(Sender: TObject);
begin
  Timer1.Enabled := false;
  SyncThread.Terminate;
end;

procedure TForm11.Timer1Timer(Sender: TObject);
begin
  StatusBar1.Panels[0].Text := 'Count: ' + IntToStr(ProgressCount);
  StatusBar1.Panels[1].Text := ProgressString;
end;

procedure TSyncThread.Execute;
var
  i : Int64;
begin
  i := 0;
  while not Terminated do begin
    inc(i);
    EnterCriticalSection(CritSect);
    ProgressString := IntToStr(i);
    ProgressCount := i;
    LeaveCriticalSection(CritSect);
  end;
end;

initialization
  InitializeCriticalSection(CritSect);
finalization
  DeleteCriticalSection(CritSect);

Я установил интервал таймера на 10 мс, чтобы он много читал, пока рабочий поток не работает, обновляя глобальную строку var. Конечно, это приложение едва длится секунду при запуске, прежде чем оно обнаружит вышеуказанные ошибки.

У меня вопрос: нужно ли запускать операцию чтения Global var в VCL Timer в критической секции? - если так, то почему? Насколько я понимаю, это только чтение, и с записями, уже запущенными в критическом разделе, я не могу понять, почему он сталкивается с проблемой. Если я помещаю чтение в таймере также в критическую секцию - это работает нормально .... но я недоволен, просто делая это, не зная, почему!

Я новичок в многопоточности, поэтому буду признателен за любую помощь в объяснении, почему этот простой пример вызывает всевозможные проблемы и есть ли лучший способ доступа к строке из рабочего потока.

1 Ответ

11 голосов
/ 15 октября 2011

Delphi String размещается в куче, где-то это не статический буфер. Сама переменная является просто указателем. Когда ваш поток чтения получает доступ к строке, и в то же время эта строка освобождается другим потоком, происходят плохие вещи . Вы обращаетесь к уже освобожденной памяти, возможно, снова выделены для чего-то еще и т. Д.

Даже если эта строка была статическим буфером, операции обновления не являются атомарными, поэтому вы можете использовать поврежденную строку, которая обновляется в данный момент (половина новых данных и половина старых).

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

...