Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a Thread Safe field of TThread itself thread safe?

in this code :

TMyClass = class(TThread)
public
  FInputBuffer     : TThreadedQueue<TBytes>;
protected
  procedure Execute; override;
end;

Does use (in TMyClass and in other class) of FInputBuffer is Thread Safe ?

EDIT:

sample use : in TMyClass :

procedure TMyClass.Execute;
var x     :TBytes;
begin
  inherited;
  FInputBuffer:= TThreadedQueue<TBytes>.Create;
  while not Terminated do begin
    if FInputBuffer.QueueSize > 0 then begin
      x:= FInputBuffer.PopItem;
      //some code to use x
    end;
  end;
  FInputBuffer.Free;
end;

In Other class :

var MyClass :TMyClass ;

procedure TForm1.btn1Click(Sender: TObject);
var x :TBytes;
begin
  //set x
  MyClass.FInputBuffer.PushItem(x);
end;
like image 843
MohsenB Avatar asked Dec 20 '22 06:12

MohsenB


1 Answers

If FInputBuffer is created in the thread constructor before the thread begins running, and is freed in the thread destructor after the thread has finished running, then yes, access to it from other threads is thread-safe while the TMyClass object is still alive, because TThreadedQueue provides its own thread-safety for its inner content. What you have shown is a perfectly valid use of a multi-threaded queue, provided the MyClass variable is valid at the time btn1Click() is called.

However, if FInputBuffer is created inside of Execute(), then it is not thread-safe, because btn1Click() might try to access the queue before the thread has started running, before FInputBuffer has been created. That is why you need to create FInputBuffer in the constructor instead, eg:

TMyClass = class(TThread)
public
  FInputBuffer: TThreadedQueue<TBytes>;
  constructor Create(ACreateSuspended: Boolean); override;
  destructor Destroy; override;
protected
  procedure Execute; override;
end;

constructor TMyClass.Create(ACreateSuspended: Boolean);
begin
  inherited;
  FInputBuffer := TThreadedQueue<TBytes>.Create;
end;

destructor TMyClass.Destroy;
begin
  FInputBuffer.Free;
  inherited;
end;

procedure TMyClass.Execute;
var
  x: TBytes;
begin
  while not Terminated do begin
    if FInputBuffer.QueueSize > 0 then begin
      x := FInputBuffer.PopItem;
      // some code to use x
    end;
  end;
end;

If you want to create FInputBuffer inside of Execute(), then thread should expose a flag/signal that gets set after FInputBuffer has actually been created, and then no other code should attempt to access FInputBuffer until that flag/signal has been set. The code that creates the thread instance should wait for that flag/signal before returning control back to the rest of the code, eg:

TMyClass = class(TThread)
public
  FInputBuffer: TThreadedQueue<TBytes>;
  FInputBufferCreated: TEvent;
  constructor Create(ACreateSuspended: Boolean); override;
  destructor Destroy; override;
protected
  procedure Execute; override;
  procedure DoTerminate; override;
end;

constructor TMyClass.Create(ACreateSuspended: Boolean);
begin
  inherited;
  FInputBufferCreated := TEvent.Create(nil, True, False, '');
end;

destructor TMyClass.Destroy;
begin
  FInputBufferCreated.Free;
  inherited;
end;

procedure TMyClass.Execute;
var
  x: TBytes;
begin
  FInputBuffer := TThreadedQueue<TBytes>.Create;
  FInputBufferCreated.SetEvent;

  while not Terminated do begin
    if FInputBuffer.QueueSize > 0 then begin
      x := FInputBuffer.PopItem;
      // some code to use x
    end;
  end;
end;

procedure TMyClass.DoTerminate;
begin
  if FInputBufferCreated <> nil then
    FInputBufferCreated.ResetEvent;
  FreeAndNil(FInputBuffer);
  inherited;
end;

.

var
  MyClass: TMyClass = nil;

procedure TForm1.StartBufferThread;
var
  I: Integer;
begin
  MyClass := TMyClass.Create(False);
  if MyClass.FInputBufferCreated.WaitFor(2500) <> wrSignaled then
  begin
    MyClass.Terminate;
    MyClass.WaitFor;
    FreeAndNil(MyClass);
    raise Exception.Create('MyClass.FInputBuffer not created after 2.5 seconds!');
  end;
end;

procedure TForm1.btn1Click(Sender: TObject);
var
  x: TBytes;
begin
  //set x
  if MyClass <> nil then
    MyClass.FInputBuffer.PushItem(x);
end;
like image 60
Remy Lebeau Avatar answered Dec 24 '22 02:12

Remy Lebeau