Recent

Author Topic: TThread in an array - freezes on closing  (Read 1028 times)

Slawek

  • New Member
  • *
  • Posts: 42
TThread in an array - freezes on closing
« on: February 08, 2023, 11:33:42 pm »
The next problem with threads I see is that I wanted to create an array of threads. Run them all together and finish with the button or close the form.
When the array is a single element: array[0..0], everything works. However with 2 elements: array[0..1] everything hangs when I click btStop or close the form. I don't understand why the program crashes.

Second case. I send ThreadID and GetCurrentThreadId to ListBox(lbThreads). ThreadID is different for each thread while GetCurrentThreadId is the same. Does GetCurrentThreadId show the id of the main thread in this situation?

The whole code looks like this:
Code: Pascal  [Select][+][-]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, ComCtrls;
  9.  
  10. type
  11.   TShowStatusEvent = procedure(Id, ThId: Integer; Status, Task: String) of Object;
  12.  
  13.   { TMyThread }
  14.  
  15.   TMyThread = class(TThread)
  16.   private
  17.     FId: Integer;
  18.     fStatusText : string;
  19.     fTaskText : string;
  20.     FOnShowStatus: TShowStatusEvent;
  21.     procedure SetId(AValue: Integer);
  22.     procedure ShowStatus;
  23.   protected
  24.     procedure Execute; override;
  25.   public
  26.     constructor Create(CreateSuspended : boolean);
  27.     property OnShowStatus: TShowStatusEvent read FOnShowStatus write FOnShowStatus;
  28.     property Id: Integer read FId write SetId;
  29.   end;
  30.  
  31.   { TForm1 }
  32.  
  33.   TForm1 = class(TForm)
  34.     btStart: TButton;
  35.     btStop: TButton;
  36.     laTask: TLabel;
  37.     laStatus: TLabel;
  38.     lbThreads: TListBox;
  39.     StatusBar1: TStatusBar;
  40.     procedure btStartClick(Sender: TObject);
  41.     procedure btStopClick(Sender: TObject);
  42.     procedure FormCreate(Sender: TObject);
  43.     procedure FormDestroy(Sender: TObject);
  44.   private
  45.     procedure ShowStatus(Id, ThId: Integer; Status, Task: string);
  46.     procedure ThreadTerminated(Sender: TObject);
  47.   public
  48.  
  49.   end;
  50.  
  51. var
  52.   Form1: TForm1;
  53.   aThreads: array[0..1] of TMyThread;
  54.  
  55. implementation
  56.  
  57. {$R *.lfm}
  58.  
  59. { TMyThread }
  60.  
  61. procedure TMyThread.ShowStatus;
  62. begin
  63.   if Assigned(FOnShowStatus) then begin
  64.     FOnShowStatus(FId, ThreadID, fStatusText, fTaskText);
  65.   end;
  66. end;
  67.  
  68. procedure TMyThread.SetId(AValue: Integer);
  69. begin
  70.   if FId=AValue then Exit;
  71.   FId:=AValue;
  72. end;
  73.  
  74. procedure TMyThread.Execute;
  75. var i: Integer;
  76. begin
  77.   fStatusText := 'Starting...';
  78.   Synchronize(@ShowStatus);
  79.   fStatusText := 'Running...';
  80.   while (not Terminated) do begin //repeat all tasks
  81.     for i:= 1 to 9 do begin
  82.       if Terminated then Break; //abort
  83.       fTaskText:='Task no '+i.ToString;
  84.       sleep(500);
  85.       if not Terminated then
  86.         Synchronize(@Showstatus);
  87.     end;
  88.   end;
  89. end;
  90.  
  91. constructor TMyThread.Create(CreateSuspended: boolean);
  92. begin
  93.   inherited Create(CreateSuspended);
  94. end;
  95.  
  96. { TForm1 }
  97.  
  98. procedure TForm1.btStartClick(Sender: TObject);
  99. var i: Integer;
  100. begin
  101.   btStart.Enabled:=False;
  102.   lbThreads.Clear;
  103.   for i:= Low(aThreads) to High(aThreads) do
  104.     lbThreads.Items.Add('.');
  105.  
  106.   for i:= Low(aThreads) to High(aThreads) do begin
  107.     aThreads[i] := TMyThread.Create(true);
  108.     aThreads[i].FreeOnTerminate := True;
  109.     aThreads[i].OnShowStatus := @ShowStatus;
  110.     aThreads[i].OnTerminate  := @ThreadTerminated;
  111.     aThreads[i].Id:=i;
  112.     laStatus.Caption:='Started';
  113.     aThreads[i].Start;
  114.   end;
  115.   btStop.Enabled:=True;
  116. end;
  117.  
  118. procedure TForm1.btStopClick(Sender: TObject);
  119. var i: Integer;
  120. begin
  121.   btStop.Enabled:=False;
  122.   for i:= Low(aThreads) to High(aThreads) do begin
  123.     aThreads[i].Terminate;
  124.     aThreads[i].WaitFor;
  125.   end;
  126.   laStatus.Caption:='Terminated';
  127.   btStart.Enabled:=True;
  128.   if Assigned(aThreads[0]) then ShowMessage('Assigned!');
  129. end;
  130.  
  131. procedure TForm1.FormCreate(Sender: TObject);
  132. begin
  133.   btStop.Enabled:=False;
  134. end;
  135.  
  136. procedure TForm1.FormDestroy(Sender: TObject);
  137. var i: Integer;
  138. begin
  139.   for i:= Low(aThreads) to High(aThreads) do begin
  140.     if Assigned(aThreads[i]) then begin
  141.       aThreads[i].Terminate;
  142.       aThreads[i].WaitFor;
  143.     end;
  144.   end;
  145. end;
  146.  
  147. procedure TForm1.ShowStatus(Id, ThId: Integer; Status, Task: string);
  148. const TXT='%S(%D/%D/%D)=%S';
  149. var cThId: Integer;
  150. begin
  151.   laStatus.Caption := Status;
  152.   laTask.Caption := Task;
  153.   cThId := GetCurrentThreadId;
  154.   lbThreads.Items[Id] := Format(TXT, [Status, Id, ThId, cThId, Task]);
  155. end;
  156.  
  157. procedure TForm1.ThreadTerminated(Sender: TObject);
  158. begin
  159.   if Sender is TMyThread then aThreads[(Sender as TMyThread).Id] := nil;
  160. end;
  161.  
  162. end.
  163.  

jamie

  • Hero Member
  • *****
  • Posts: 6077
Re: TThread in an array - freezes on closing
« Reply #1 on: February 09, 2023, 02:36:07 am »
if you do a Waitfor on a thread that never got started or was placed in suspend mode it may hang there at the Waitfor.

 Don't use Waitfor unless you know the thread is actually running.

This could be one of those little bugs we don't like to talk about.! Oh No, Say it's not so!

The only true wisdom is knowing you know nothing

Slawek

  • New Member
  • *
  • Posts: 42
Re: TThread in an array - freezes on closing
« Reply #2 on: February 09, 2023, 02:52:24 pm »
Hello jamie,

if you do a Waitfor on a thread that never got started or was placed in suspend mode it may hang there at the Waitfor.

 Don't use Waitfor unless you know the thread is actually running.
Thank you for your comments.
I deleted both Waitfors and now there is indeed no freeze.
1.
But shouldn't I wait for all threads to finish when closing the application and/or form?
2.
I don't understand this. If I tell you to terminate threads (aThreads.Terminate), then why aThreads.WaitFor; freezes? So how can I make sure threads are actually terminated here?

This could be one of those little bugs we don't like to talk about.! Oh No, Say it's not so!
I like to talk about all the bugs, because only then will I be able to write correct code.
It annoys me when I don't understand something and make stupid mistakes.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: TThread in an array - freezes on closing
« Reply #3 on: February 09, 2023, 03:54:53 pm »
For this purpose there exist a special synchronization datastructure called a barrier: https://en.m.wikipedia.org/wiki/Barrier_(computer_science)
 At its core it works like this, a barrier requires a certain number of threads to break through. A thread that runs into the barrier waits until enough threads are waiting and then all of them will continue at once.

I always think of it as a race track, where everyone waits at the start line until all are in place and then the race starts.

In your program you can use a barrier to wait for the threads. When starting the threads you setup the barrier with a threshold of n+1 (where n is the number of threads). In the on terminate of your threads you let them enter the barrier. When waiting for all threads your main thread enters the barrier as well. As soon as all the threads are finished, and the main thread is also at the point of entering the barrier, the barrier releases, all the threads that are waiting will continue with their FreeOnTerminate, and the main thread will continue execution knowing that all other threads just finished
« Last Edit: February 09, 2023, 03:56:48 pm by Warfley »

alpine

  • Hero Member
  • *****
  • Posts: 1032
Re: TThread in an array - freezes on closing
« Reply #4 on: February 09, 2023, 04:07:13 pm »
@Slawek
You are making the same mistake I wrote about:
https://forum.lazarus.freepascal.org/index.php/topic,62182.msg470478.html#msg470478

Just comment the:
Code: Pascal  [Select][+][-]
  1.     aThreads[i].FreeOnTerminate := True;
in the btStartClick and you'll see the difference.

You're calling WaitFor on a dangling reference!
"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Slawek

  • New Member
  • *
  • Posts: 42
Re: TThread in an array - freezes on closing
« Reply #5 on: February 09, 2023, 04:37:36 pm »
@Slawek
You are making the same mistake I wrote about:
https://forum.lazarus.freepascal.org/index.php/topic,62182.msg470478.html#msg470478
Yes. I already know it's a mistake. I have read your advice, but I need to calmly analyze it and read more about it. I wanted to reply to you when I can test it. I have other urgent matters at the moment.
I've noticed that there's a lot of bad threading advice on the internet. This makes it difficult to understand the topic. I don't always know which advice is bad and which is good.

Slawek

  • New Member
  • *
  • Posts: 42
Re: TThread in an array - freezes on closing
« Reply #6 on: February 09, 2023, 04:40:31 pm »
For this purpose there exist a special synchronization datastructure called a barrier: https://en.m.wikipedia.org/wiki/Barrier_(computer_science)
Thank you. I'll be interested in it. I might find it useful  :)

Thaddy

  • Hero Member
  • *****
  • Posts: 14158
  • Probably until I exterminate Putin.
Re: TThread in an array - freezes on closing
« Reply #7 on: February 09, 2023, 04:42:22 pm »
For this purpose there exist a special synchronization datastructure called a barrier:

And in Freepascal that is already implemented in the system unit. readwrite, readdependency, read and write barriers are supported from 3.2.2 or higher.
https://www.freepascal.org/docs-html/rtl/system/readwritebarrier.html

Pity Warfley forgot to mention that.
It is also very low level and not suited to beginners.
« Last Edit: February 09, 2023, 04:50:13 pm by Thaddy »
Specialize a type, not a var.

Warfley

  • Hero Member
  • *****
  • Posts: 1499
Re: TThread in an array - freezes on closing
« Reply #8 on: February 10, 2023, 10:24:31 am »
You don't need to use the complicated fpc barriers both pthread and winapi barriers are very easy to use. A few years ago I've wrote a wrapper for both of those: https://github.com/Warfley/FPCBarrier/blob/master/barrier.pas
If you focus on one system it is very easy, but even with the ifdefs it is just 80 lines of code

 

TinyPortal © 2005-2018