[Okta Webinar] Learn how to a build a cloud-first strategyRegister Now

x
  • Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 286
  • Last Modified:

Threads - working with Critical sections

Hi there =)
I am going to be doing some work with threads and will be using critical sections and have a question (to save hours of debugging later).

If I have a thread that does this


like global var
  CriticalSection : TRTLCriticalSection;

then

  InitializeCriticalSection(CriticalSection);
  Thread1Handle := BeginThread(nil, 1024, @StartThread1, nil,0,Thread1ID);
  Thread2Handle := BeginThread(nil, 1024, @StartThread2, nil,0,Thread2ID);

then in the code do as above


EnterCriticalSection(CriticalSection);
Step #1 fetch item from global string list and remove the item from that string list.
LeaveCriticalSection(CriticalSection);

Step #2 process this item

EnterCriticalSection(CriticalSection);
Step #3 output the results to a memo
LeaveCriticalSection(CriticalSection);

For that, could I use the same critical section twice?
I should I have two critical sections?

I guess the other way is to do this in the thread

begin
{start loop while the stringlist has items in it (count > 0)}
EnterCriticalSection(CriticalSection);
  --> push results if exist
  --> flush results string
  --> Step #1 fetch item from global string list and remove the item from that string list.
LeaveCriticalSection(CriticalSection);

Step #2 process the item

{end loop}

No sure how the loop would look like though...
mmmmm
500 points for this one :D



0
wildzero
Asked:
wildzero
  • 5
  • 5
3 Solutions
 
TheRealLokiSenior DeveloperCommented:
you can use the same critical section
I personally don't like to use TCriticalSections, and would rather use a synchronize in this situation, but it's up to you.
0
 
wildzeroAuthor Commented:
Do you have some sample code of how you would use synchronize in this situation?

0
 
TheRealLokiSenior DeveloperCommented:
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type TWorkerThread = class(TThread)
private
  TopStringFromGlobalList: string;
  myname: string;
//synchronized calls
  procedure GetTopString;
  procedure DisplayString;
public
  constructor create(myname_: string);
  procedure execute; override;
end;

type
  TForm1 = class(TForm)
    Edit1: TEdit;
    Memo1: TMemo;
    bStartThreads: TButton;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Edit1KeyDown(Sender: TObject; var Key: Word;
      Shift: TShiftState);
    procedure bStartThreadsClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    GlobalSL: TStringList;
    WorkerThread1, WorkerThread2: TWorkerThread;
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
    GlobalSL := TStringList.create;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  GlobalSL.Clear;
  GlobalSL.Free;
end;

procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
  Shift: TShiftState);
begin
  if key = vk_return then
  begin
      GlobalSL.Add( Edit1.Text ); // add a string to our list
      key := 0;
  end;
end;

procedure TForm1.bStartThreadsClick(Sender: TObject);
begin
  WorkerThread1 := TWorkerThread.Create('1');
  WorkerThread1 := TWorkerThread.Create('2');
end;

{ TWorkerThread }

constructor TWorkerThread.create(myname_: string);
begin
  inherited create(True);
  myname := myname_;
  freeonterminate := true;
  resume;
end;

procedure TWorkerThread.DisplayString;
begin
  Form1.memo1.lines.add(TopStringFromGlobalList);
end;

procedure TWorkerThread.execute;
begin
  while not terminated do
  begin
      Synchronize(GetTopString);
      if TopStringFromGlobalList <> '' then
      begin
          TopStringFromGlobalList := 'Processed by thread ' + MyName + ': "' + TopStringFromGlobalList + '"';
          Synchronize(DisplayString);
          sleep(1000); // wait 1 second before checking again
      end;
  end;
end;

procedure TWorkerThread.GetTopString;
begin
  if Form1.GlobalSL.Count > 0 then
  begin
      TopStringFromGlobalList := Form1.GlobalSL[0];
      Form1.GlobalSL.Delete(0);
  end
  else
    TopStringFromGlobalList := '';
end;

end.
0
Independent Software Vendors: We Want Your Opinion

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
wildzeroAuthor Commented:
Wow that is some good looking code, few minor errors
 WorkerThread1 := TWorkerThread.Create('1');
  WorkerThread1 := TWorkerThread.Create('2');

should be

 WorkerThread1 := TWorkerThread.Create('1');
 WorkerThread2 := TWorkerThread.Create('2');

but other  then that looks really good....

just thinking, is it possible to have a array of TWorkerThread?
That way I could start 1 or 3 or 10 threads without any real hassle adding more variables?

Thanks
0
 
TheRealLokiSenior DeveloperCommented:
lol oops, sorry
actually, you don't need to declare the threads as variables at all, i just did so, so you could easily terminate them if you wanted

you can just go
for i := 1 to 10 do Tworkerthread.create(inttostr(i));
if you want

to have a list of threads, I'll show you with just a TList, which should be fine
(there is a TThreadlist if you need a threadsafe list)


var
  mythreadlist: TList;
...
  mythreadlist := TList.create;

mythreadlist.add( TWorkerThread.Create('1') );
mythreadlist.add( TWorkerThread.Create('2') );

then, when closing, count backwards through your list and stop the threads
for i := Pred(mythreadlist.count) downto 0 do
begin
    TWorkerThread( mythreadlist[i] ).Terminate;
    mythreadlist.delete(i);
end;
MyThreadList.Free;
0
 
wildzeroAuthor Commented:
Awesome
sounds like the way to go ;-)

So just to clarrify
Any procedure decleared in the Tworkerthread class can then be sycronised
like procedure GetTopString;
which can then be Synchronize(GetTopString)

so only items that are decleared there can be Synchronized?

Last question, honest
0
 
TheRealLokiSenior DeveloperCommented:
yep, you can only synchronize methods in your thread class
just dont put them in the public block, and I tend to comment them so it's obvious.

Don't synchronize everything though, only synchronize when you need to access the main VCL.
If you synchronize everything, you end up defeating the purpose of having threads

funny fact: if you were to call synchronize in the main thread (i.e. form1) you'd get an infinite loop
0
 
wildzeroAuthor Commented:
Cool
Ok so I found another question (sorry)
Is it safe to call procedures/functions outside the thread but don't use VCL components?

Ie, call a function that takes a value and adds 1 to it.

Cheers
0
 
TheRealLokiSenior DeveloperCommented:
short answer: yes, if you are sure of everything the code does.
If the function can in anyway affect the main vcl, you should synchronize access to it.
However, it is safe to use simple routines that only affect the variables you pass.
e.g
s := TRIM(S);

But if it was...

TForm1
public
    fGlobalString: string;
...

..
procedure TForm1.SetGlobalString(value: string);
begin
    fGlobalString := value;
end;

Calling SetGlobalString would be an unsafe routine to call from your thread.
something else may be accessing "fGlobalString" apart from your thread.

Thread errors can be hard to track down, you may get random access violations, which leave you guessing, so when in doubt, synchronize.


you can have as many methods as you like, treat it like any other class.
you only need to synchronize if you need to access something outside of your thread class.
e.g. I synchronize when i need to update the display, or change shared settings, or get a value from the form, etc...

but "synchronize" methods can not take any parameters
which is why you set the values, and then call synchronize
e.g.
type TWorkerThread = class(TThread)
private
  InfoMsg: string; // this is used when we synchronize the "DisplayInfoMessageInMainVCL" procedure

  Procedure CountTo10; // done in thread context
  Function AddHelloToString(s: string): string;
//synchronized calls
  procedure DisplayInfoMessageInMainVCL; //done in main thread context when "Synchronized"
...
...
procedure TWorkerThread.Execute;
begin
    while not terminated do
    begin
        CountTo10; // another procedure in this thread
    end;
end;

procedure TWorkerThread.CountTo10; // done in thread context
var
    i: integer;
    s: string;
begin
    for i := 1 to 10 do
    begin
        s := IntToStr(i);
        InfoMsg := AddHelloToString(s); // still done in thread context
        synchronize(DisplayInfoMessageInMainVCL); // done in main thread context
    end;
end;

function TWorkerThread.AddHelloToString(s: string): string;
begin
    result := 'Hello ' + s;
end;

procedure TWorkerThread.DisplayInfoMessageInMainVCL; // done in main thread context when "Synchronized"
begin
    form1.memo1.lines.add(InfoMsg); //InfoMsg is a private property of this thread. it is set before we call this procedure
end;
0
 
wildzeroAuthor Commented:
Thanks very much, your posts in this thread have made it a very good source for ThreadInfo
0

Featured Post

VIDEO: THE CONCERTO CLOUD FOR HEALTHCARE

Modern healthcare requires a modern cloud. View this brief video to understand how the Concerto Cloud for Healthcare can help your organization.

  • 5
  • 5
Tackle projects and never again get stuck behind a technical roadblock.
Join Now