Recent

Author Topic: Looking for threading advice[SOLVED]  (Read 3029 times)

jbmckim

  • Jr. Member
  • **
  • Posts: 84
Looking for threading advice[SOLVED]
« on: February 07, 2018, 03:30:41 am »
I'm working on an instrument that takes input from 1 to n other instruments (where n is < 33).  Think of these as data servers. I've done this sort of thing a couple times in .Net. In those apps, each instrument is tied to an instance of a class that completely completely contains the attributes and actions necessary to support a given instrument.  All the respective instruments are gathered into a "Collection" (in the case of VB.Net).  In VB.Net, the "ForEach" construct can be used to step trough a collection and reference each class found there of a specific type.  So, on to what's happening now in VB.Net:

Code: Pascal  [Select]
  1. Parallel.ForEach(ServerInstrumenCollection.Cast(Of ServerInstrumentClass), po, (Sub(SI As ServerInstrument) SI.TakeData()))

Working from right to left, the procedure "TakeData" in the SI object, acquires data from instruments, each of which is owned by a ServerInstrument class.  Each ServerInstrument class instance constitutes an element of ServerInstrumentCollection.  The construct above will execute SI.TakeData for each element of ServerInstrumentCollection in a discrete thread, blocking until all are complete.  I'd like to mimic that logic model if the code associated with it in lazpas isn't prohibitive.

I'm looking to do something similar in Lazarus Pascal. (I realize a good deal more code my be required.  I'm speaking algorithmically.)

What would be the best way to mimic the .Net architecture described above?

Thanks.

« Last Edit: March 30, 2018, 08:19:43 pm by jbmckim »

ASerge

  • Hero Member
  • *****
  • Posts: 997
Re: Looking for threading advice
« Reply #1 on: February 07, 2018, 05:13:13 am »
I'm looking to do something similar in Lazarus Pascal. (I realize a good deal more code my be required.  I'm speaking algorithmically.)
Search in google for the text "multithreaded library fpc" led to https://github.com/BeRo1985/pasmp/blob/master/examples/parallelfor/parallelfor.dpr

Nitorami

  • Sr. Member
  • ****
  • Posts: 346
Re: Looking for threading advice
« Reply #2 on: February 07, 2018, 09:33:39 am »
Look at unit mtprocs.

 ProcThreadpool.DoParallel (@myfunction, startindex, endindex, @Data);

SymbolicFrank

  • Sr. Member
  • ****
  • Posts: 394
Re: Looking for threading advice
« Reply #3 on: February 09, 2018, 04:28:29 pm »
Code: Pascal  [Select]
  1. type
  2.   ServerInstrumentClass = class(TThread)
  3.     procedure Execute;
  4.     procedure ReadYourInstrument; virtual; abstract;
  5.   end;
  6.  
  7.   SomeInstrumentClass = class(ServerInstrumentClass)
  8.     procedure ReadYourInstrument; override;
  9.   end;
  10.  
  11.   InstrumentList = specialize TFPGObjectList<ServerInstrumentClass>;
  12.  
  13. procedure ServerInstrumentClass.Execute;
  14. begin
  15.   while not Terminated do
  16.   begin
  17.     ReadYourInstrument;
  18.   end;
  19. end;
  20.  
  21. procedure SomeInstrumentClass.ReadYourInstrument;
  22. begin
  23.   // Do the stuff
  24. end;
  25.  


jbmckim

  • Jr. Member
  • **
  • Posts: 84
Re: Looking for threading advice
« Reply #4 on: March 08, 2018, 06:00:01 am »
Sorry I haven't been back to this in awhile.  After looking at the initial responses, it seemed I needed to get a little more work done to narrow the focus.

Consider a class:

 
Code: Pascal  [Select]
  1.  TInstrument = Class
  2.      private
  3.      ...
  4.  
  5.      public
  6.      ...
  7.      procedure GetData();
  8.      ...                      
  9.  

This class in turn gets loaded into an object list:

Code: Pascal  [Select]
  1. type
  2.   TInstList = specialize TFPGObjectList<TInstrument>;
  3.  
  4.  var
  5.   InstrumentList : TInstList;
  6.   InstObj: InstrumentUnit.TInstrument;  
 

The goal is to execute multiple threads of GetData() simultaneously.  Thoughts?


taazz

  • Hero Member
  • *****
  • Posts: 5362
Re: Looking for threading advice
« Reply #5 on: March 08, 2018, 06:41:23 am »
Sorry I haven't been back to this in awhile.  After looking at the initial responses, it seemed I needed to get a little more work done to narrow the focus.

Consider a class:

 
Code: Pascal  [Select]
  1.  TInstrument = Class
  2.      private
  3.      ...
  4.  
  5.      public
  6.      ...
  7.      procedure GetData();
  8.      ...                      
  9.  

This class in turn gets loaded into an object list:

Code: Pascal  [Select]
  1. type
  2.   TInstList = specialize TFPGObjectList<TInstrument>;
  3.  
  4.  var
  5.   InstrumentList : TInstList;
  6.   InstObj: InstrumentUnit.TInstrument;  
 

The goal is to execute multiple threads of GetData() simultaneously.  Thoughts?

Look at unit mtprocs.

 ProcThreadpool.DoParallel (@myfunction, startindex, endindex, @Data);

here is a complete unit that demonstrates how things work with mtProcs. I'll post a similar example for pasmp later.
Code: Pascal  [Select]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, fgl, FileUtil, Forms, Controls, Graphics, Dialogs, MTProcs;
  9.  
  10. type
  11.  
  12.   { TInstrument }
  13.  
  14.   TInstrument = Class
  15.   private
  16.   public
  17.     procedure GetData;
  18.   end;
  19.   TInstList = specialize TFPGObjectList<TInstrument>;
  20.  
  21.   { TForm1 }
  22.  
  23.   TForm1 = class(TForm)
  24.   private
  25.     FInstrumentList :TInstList;
  26.   public
  27.     procedure TestMtProcs;
  28.   end;
  29.  
  30. var
  31.   Form1 : TForm1;
  32.  
  33. implementation
  34.  
  35. {$R *.lfm}
  36.  
  37.   //TMTMethod = procedure(Index: PtrInt; Data: Pointer;
  38.   //                      Item: TMultiThreadProcItem) of object;
  39.   //TMTProcedure = procedure(Index: PtrInt; Data: Pointer;
  40.   //                         Item: TMultiThreadProcItem);
  41.  
  42. procedure DoData(Index:PtrInt; Data:Pointer; Item: TMultiThreadProcItem);
  43. begin
  44.   TInstList(Data)[Index].GetData;
  45. end;
  46.  
  47. { TInstrument }
  48.  
  49. procedure TInstrument.GetData;
  50. begin
  51.   Sleep(2000);
  52. end;
  53. //Procedure
  54. { TForm1 }
  55.  
  56. procedure TForm1.TestMtProcs;
  57. begin
  58.   ProcThreadpool.DoParallel(@DoData, 0, FInstrumentList.count -1, Pointer(FInstrumentList))
  59. end;
  60.  
  61. end.
  62.  
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

SymbolicFrank

  • Sr. Member
  • ****
  • Posts: 394
Re: Looking for threading advice
« Reply #6 on: March 08, 2018, 08:37:09 am »
In the example I posted, you can just create all the threads and add them to the list. As long as you don't create them suspended, they will automatically start and do their stuff in parallel.

Code: Pascal  [Select]
  1.  public constructor TThread.Create(
  2.   CreateSuspended: Boolean;
  3.   const StackSize: SizeUInt = DefaultStackSize
  4. );

Don't make it more complex than it has to be.

Thaddy

  • Hero Member
  • *****
  • Posts: 7178
Re: Looking for threading advice
« Reply #7 on: March 08, 2018, 10:08:31 am »
Indeed. And CreateSuspended is deprecated anyway. Threads should be created and run immediately on demand, not started on demand.
« Last Edit: March 08, 2018, 10:10:37 am by Thaddy »
inline variables like in D10.3 are a bit like Brexit: if you are given the wrong information it sounds like a good idea. Every kid loves candy, but it makes you fat and your teeth will disappear.

taazz

  • Hero Member
  • *****
  • Posts: 5362
Re: Looking for threading advice
« Reply #8 on: March 08, 2018, 10:13:18 am »
Indeed. And CreateSuspended is deprecated anyway. Threads should be created and run immediately on demand, not started on demand.
so how does pooling work with threads then?
Good judgement is the result of experience … Experience is the result of bad judgement.

OS : Windows 7 64 bit
Laz: Lazarus 1.4.4 FPC 2.6.4 i386-win32-win32/win64

Thaddy

  • Hero Member
  • *****
  • Posts: 7178
Re: Looking for threading advice
« Reply #9 on: March 08, 2018, 11:24:29 am »
@taazz:
In that case the threads are still taking up resources....Which you may not want or you may depending on application.
99% on the forum does not know how to handle threads correctly and 100% want threads. (and 99% wants quick interaction with a GUI or a shared resource in general, which defeats the questions (not the purpose) I don't have a moron available, so I have to, contrary to my promise, be grumpy < >:D>)
If you mean suspend has issues... that is true. More so than resume or start.
That's IMNSHO

This is a recurring issue. Many people already explained why. In the case of a thread-pool you may have a point, but in that case I usually have them spinning for a new - small - job.

So I am not talking theory any more. It is hosted on my website. Or google Martin Harvey. (Although he discusses the perils *before* any deprecation)
« Last Edit: March 08, 2018, 01:40:00 pm by Thaddy »
inline variables like in D10.3 are a bit like Brexit: if you are given the wrong information it sounds like a good idea. Every kid loves candy, but it makes you fat and your teeth will disappear.

SymbolicFrank

  • Sr. Member
  • ****
  • Posts: 394
Re: Looking for threading advice
« Reply #10 on: March 08, 2018, 12:41:37 pm »
There are roughly two kinds of threads: the ones which spend most of their time waiting, and the ones that work hard for a short period of time.

The waiting threads should just be created on-demand. If you want to communicate with them, create a thread-safe queue and give them a pointer to it. Or use a socket. It doesn't matter much. Try hard not to use shared memory.

You can mostly ignore them after creation, it doesn't matter if they wait for days at a time. And if the application terminates, they will be removed automatically. But it is better to have them periodically check if they should terminate, so they can clean up after themselves.

The threads that work hard for a short period of time are often collected in a thread-pool, as to have a maximum that is allowed to be active at any time. And often they are used by having them all run the same task, but on different pieces of some shared memory. And you wait until they're all finished.

Which is completely wrong. In that case it is most of the time faster just to use a single thread to do the calculations.

The only good way to divide a big task and run it in parallel, is to first split the data into separate, independent pieces. And for each piece of data, you start a separate thread. Give them a way to post back the result (queue or socket, or write it to disk) and forget about them. They should terminate and clean up when they're done.

And, as the separating of the data takes time, as well as the rebuilding of it afterwards, it won't cause much problems if you just start a task (thread) when you're ready to do so.

Even in that case, the bottleneck is in the process that coordinates it. So it's probably not faster than just doing it all in a single thread.


A good example of how to do it is a server process: it sits on a socket, waiting for activity. If something happens, it starts a new thread to handle the connection. Which probably spawns a new thread to process each transaction.

All those threads do their thing, terminate and clean themselves up, except perhaps the server process itself.

And if you want them to communicate with their parent: give them a pointer to a queue, or make sure the parent waits with terminating until all the child-threads have terminated.


Inter-thread communication is tricky business, because you either don't know if the receiver still exists (self terminating) or if it is still doing the same task (thread pool).

Thaddy

  • Hero Member
  • *****
  • Posts: 7178
Re: Looking for threading advice
« Reply #11 on: March 08, 2018, 12:44:07 pm »
Ok your made your point: actually there are 3 kind of threads: you and me wrote on it.... Plz refer to either DOCUMENTATION or better:EDUCATION.
Screen painters can't be let loose on threaded programming......usually... So basically I agree with you  O:-) :o
« Last Edit: March 08, 2018, 12:47:07 pm by Thaddy »
inline variables like in D10.3 are a bit like Brexit: if you are given the wrong information it sounds like a good idea. Every kid loves candy, but it makes you fat and your teeth will disappear.

SymbolicFrank

  • Sr. Member
  • ****
  • Posts: 394
Re: Looking for threading advice
« Reply #12 on: March 08, 2018, 01:12:19 pm »
Nice.  8-)

jbmckim

  • Jr. Member
  • **
  • Posts: 84
Re: Looking for threading advice
« Reply #13 on: March 10, 2018, 07:11:43 pm »
I fleshed out taazz's example some for those that might come after.  I tried to attach the project as a .zip when I posted this before but apparently it didn't post.  (Too big?)

If you feel like looking this over and offering any critiques, it will be appreciated. 

I've added a few calls to ProcessMessages in order to make the UI more responsive.  This still isn't the best.  Is there a better way to do this given this implementation of multithreading?  My eventual application "loop" will slave off a TTimer event so that may improve things some.

I'll keep this post open for a couple more days and then marked SOLVED.  Thanks everyone for the input.  It was very helpful.

Code: Pascal  [Select]
  1. unit Unit1;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, SysUtils, fgl, FileUtil, Forms, Controls, Graphics, Dialogs,
  9.   StdCtrls, Spin, MTProcs;
  10.  
  11. type
  12.  
  13.   { TInstrument }
  14.  
  15.   TInstrument = Class
  16.   private
  17.   public
  18.     procedure GetData(index : integer);
  19.   end;
  20.   TInstList = specialize TFPGObjectList<TInstrument>;
  21.  
  22.   { TForm1 }
  23.  
  24.   TForm1 = class(TForm)
  25.     ThreadsRunningLabel: TLabel;
  26.     NumberOfThreadsLabel: TLabel;
  27.     NumThreadsSpinEdit: TSpinEdit;
  28.     StartButton: TButton;
  29.     StopButton: TButton;
  30.     procedure StopButtonClick(Sender: TObject);
  31.     procedure StartButtonClick(Sender: TObject);
  32.     procedure DoData(Index:PtrInt; Data:Pointer; Item: TMultiThreadProcItem);
  33.   private
  34.     FInstrumentList :TInstList;
  35.     FInstrument : TInstrument;
  36.   public
  37.     procedure TestMtProcs;
  38.   end;
  39.  
  40. var
  41.   Form1 : TForm1;
  42.   bEnd  : boolean = false;
  43. implementation
  44.  
  45. {$R *.lfm}
  46.  
  47. procedure TForm1.DoData(Index:PtrInt; Data:Pointer; Item: TMultiThreadProcItem);
  48. var
  49.   BoxStyle : integer = 0;
  50.  
  51. begin
  52.  
  53.   TInstList(Data)[Index].GetData(Index);
  54.  
  55. end;
  56.  
  57. { TInstrument }
  58.  
  59. procedure TInstrument.GetData(index : integer);
  60. var
  61.   i : integer = 0;
  62.  
  63. begin
  64.  
  65.   for i := 0 to 10 do
  66.     Sleep(10);
  67.  
  68. end;
  69.  
  70. procedure TForm1.StartButtonClick(Sender: TObject);
  71. var
  72.   iNumThreads : integer = 0;
  73.   i           : integer = 0;
  74.   app         : TApplication;
  75.  
  76. begin
  77.  
  78.   FInstrument := TInstrument.Create;
  79.  
  80.   StartButton.Enabled := false;
  81.   StopButton.Enabled  := true;
  82.  
  83.   StartButton.Refresh;
  84.   StopButton.Refresh;
  85.  
  86.   app := TApplication.Create(nil);
  87.   app.ProcessMessages;
  88.  
  89.   bEnd := false;
  90.  
  91.   FInstrumentList := TInstList.Create(false);
  92.  
  93.   iNumThreads := NumThreadsSpinEdit.Value;
  94.  
  95.   for i := 0 to (iNumThreads - 1) do
  96.   begin
  97.     FInstrumentList.Add(FInstrument);
  98.   end;
  99.  
  100.   TestMtProcs();
  101.  
  102.   StartButton.Enabled := true;
  103.   StopButton.Enabled := false;
  104.  
  105. end;
  106.  
  107. procedure TForm1.StopButtonClick(Sender: TObject);
  108. var
  109.   app         : TApplication;
  110.  
  111. begin
  112.  
  113.   app := TApplication.Create(nil);
  114.   app.ProcessMessages;
  115.  
  116.   StartButton.Enabled := true;
  117.   StopButton.Enabled := false;
  118.   bEnd := True
  119.  
  120. end;
  121.  
  122. procedure TForm1.TestMtProcs;
  123. var
  124.   i : integer = 0;
  125.   app: TApplication;
  126.  
  127. begin
  128.  
  129.   bEnd := false;
  130.   app := TApplication.Create(nil);
  131.  
  132.   ThreadsRunningLabel.Caption:= IntToStr(FInstrumentList.count) + ' threads running';
  133.  
  134.   while (bEnd = false) do
  135.   begin
  136.     app.ProcessMessages;
  137.     ProcThreadpool.DoParallel(@DoData, 0, FInstrumentList.count -1, Pointer(FInstrumentList));
  138.   end;
  139.  
  140.   ThreadsRunningLabel.Caption:='No Threads Running';
  141.  
  142.  
  143. end;
  144.  
  145. end.
  146.  
  147.                                                    

engkin

  • Hero Member
  • *****
  • Posts: 2121
Re: Looking for threading advice
« Reply #14 on: March 10, 2018, 11:28:35 pm »
I think you need to take it easy before you mark this thread as "SOLVED".

I see a few issues here:


TApplication:
  • Creating an instance of TApplication ( twice ) does not seem right. There is one already created for you. It is in a global variable called Application in unit Forms.
  • There is no need to create an instance of TApplication.
  • Should not need to call Application.ProcessMessages.


StartButtonClick:
  • The code in StartButtonClick assumes that threads will start, work, and finish inside this method. That is not true.
  • StartButtonClick is to start the threads and prevent the user from trying to start them again.
  • It seems there is another assumption that all threads are sharing one FInstrument. Does not seem right.

There is more. I leave the rest to someone else.