Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Delphi - Updating a global string from a second thread

I am experimenting with multithreading in Delphi (XE) and have run into a problem with the use of a Global Variable between the main VCL thread and a second work thread.

My project involves a 2nd worker thread that scans through some files, and updates a globalvar string with the current filename its on. This globalvar is then picked up via a timer on the main VCL thread, and updates a statusbar.

I have noticed though that it occasionally comes up with a 'Invalid Pointer Operation'...or 'Out of Memory' or the work thread just stops responding (deadlock probably).

I therefore created a test app to identify and greatly increase the chance of error so i could see what's going on.

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);

I set the timer interval to 10ms so that it is reading a lot, whilst the worker thread is running flat out updating the global var string. Sure enough this app barely lasts a second when run before it comes up with the above errors.

My question is, does the read operation of the Global var in the VCL Timer need to be run in a critical section? - if so, why?. From my understanding it is only a read, and with the writes already running in a critical section, i cannot see why it runs into a problem. If i do put the read in the timer into a critical section as well - it works fine....but im unhappy just doing that without knowing why!

I am new to multithreading so would appreciate any help in explaining why this simple example causes all sorts of problems and if there is a better way to be accessing a string from a worker thread.

like image 769
froudeg Avatar asked Oct 15 '11 16:10

froudeg


1 Answers

Delphi String is allocated on a heap, it is not a static buffer somewhere. The variable itself is just a pointer. When your reading thread accesses a String, and at the same time this very string is being deallocated by another thread, bad things happen. You are accessing already freed memory, possibly allocated again for something else, etc.

Even if this String was a static buffer, update operations are not atomic, therefore you could be using a corrupted string that is being updated at this very moment (half new data and half old).

So you need to protect your reading operations with the same critical section you used around the writing operations.

like image 161
haimg Avatar answered Jan 01 '23 17:01

haimg