#multithreading #delphi #delphi-xe
#многопоточность #delphi #delphi-xe
Вопрос:
Я экспериментирую с многопоточностью в 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 мс, чтобы он много читал, в то время как рабочий поток работает, обновляя глобальную переменную строку. Конечно, это приложение едва длится секунду при запуске, прежде чем оно выдает вышеуказанные ошибки.
Мой вопрос в том, нужно ли запускать операцию чтения глобальной переменной в таймере VCL в критической секции? — если да, то почему?. Насколько я понимаю, это только чтение, и поскольку записи уже выполняются в критической секции, я не понимаю, почему это приводит к проблеме. Если я также помещаю чтение в таймере в критический раздел — это работает нормально …. но я недоволен, просто делая это, не зная почему!
Я новичок в многопоточности, поэтому был бы признателен за любую помощь в объяснении, почему этот простой пример вызывает всевозможные проблемы, и если есть лучший способ получить доступ к строке из рабочего потока.
Комментарии:
1. Знаете ли вы, что вы можете избавиться от блокировки и просто использовать целочисленную переменную для обмена данными между потоками? Это немного запутанно с 64-битным целым числом, но тривиально с 32-битным целым числом.
Ответ №1:
Строка Delphi выделяется в куче, это не статический буфер где-то. Сама переменная — это просто указатель. Когда ваш поток чтения обращается к строке, и в то же время эта самая строка освобождается другим потоком, происходят плохие вещи. Вы обращаетесь к уже освобожденной памяти, возможно, выделенной снова для чего-то другого и т.д.
Даже если эта строка была статическим буфером, операции обновления не являются атомарными, поэтому вы можете использовать поврежденную строку, которая обновляется в этот самый момент (половина новых данных и половина старых).
Поэтому вам необходимо защитить свои операции чтения тем же критическим разделом, который вы использовали для операций записи.
Комментарии:
1. Спасибо! — это был невероятно быстрый ответ, который подробно объясняет проблему — именно то, что я хотел. Мне всегда нравится знать, что происходит, а не просто слепо делать что-то, потому что это работает.