Delphi - 如何从第二个线程更新全局字符串

9
我正在尝试在Delphi (XE)中进行多线程实验,并遇到了一个问题,即主VCL线程和第二个工作线程之间使用全局变量的问题。
我的项目涉及第二个工作线程,它扫描一些文件,并使用当前文件名更新globalvar字符串。然后,主VCL线程上的定时器会获取这个globalvar,并更新状态栏。
但是我注意到它偶尔会出现“无效指针操作”、“内存不足”或者工作线程停止响应(可能是死锁)。
因此,我创建了一个测试应用程序来识别并大大增加错误的机会,以便我可以看到发生了什么。
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计时器中全局变量的读操作是否需要在临界区内运行?如果需要,为什么?根据我的理解,这只是一次读取,由于写操作已经在临界区内运行,我看不出它为什么会遇到问题。如果我也将计时器中的读取放入临界区中,它就可以正常工作...但我不愿意这样做而不知道原因!
我对多线程还很陌生,希望能得到任何关于为什么这个简单的例子会导致各种问题以及访问工作线程中字符串的更好方法的帮助。

你知道吗,你可以摆脱锁定,只需使用一个整数变量就可以在线程之间共享数据?使用64位整数有点复杂,但使用32位整数却相当简单。 - David Heffernan
1个回答

11

Delphi字符串被分配在堆上,它不是静态缓冲区。该变量本身只是一个指针。当你的读取线程访问一个字符串,并且同时这个字符串正在被另一个线程释放时,就会发生糟糕的事情。您正在访问已经释放的内存,可能已经重新为其他某些内容分配了内存等。

即使此字符串是静态缓冲区,更新操作也不是原子的,因此您可能正在使用正在此时被更新的已损坏的字符串(一半是新数据,一半是旧数据)。

因此,您需要使用与写操作周围使用的相同的关键节保护您的读取操作。


谢谢!- 这是一个非常快速的回复,并详细解释了问题 - 正是我想要的。我总是喜欢知道发生了什么,而不是盲目地做某些事情,因为它有效。 - froudeg

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接