Recent

Author Topic: Learning Threads  (Read 5730 times)

tudi_x

  • Hero Member
  • *****
  • Posts: 532
Learning Threads
« on: July 20, 2017, 11:25:11 am »
hi All,
basically i am learning about threads. as per my understanding the Execute method should run as soon as the thread is started.
please advise on the attached code as the writeln in the Execute method looks like does not run.

please also advise on the thread destructor. it looks like one block is stuck in heap. am i correctly freeing the thread?

thank you

Lazarus 1.8 RC3 on Windows 7 64bit
Lazarus 2.0.2 64b on Debian LXDE 10

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Learning Threads
« Reply #1 on: July 20, 2017, 11:47:06 am »
If you compile your code, what does it warn about ?

(I'm merely trying to get yourself acquaint with the compiler messages, as they are outputted for a reason. Ignoring those warnings makes your experience much harder).

Example can be found in wiki here..

I can also provide you with the answer to your question, but try yourself first.

Additionally, try to read the class documention on TThread, here.

Did you noticed what it reads on execute method ?
« Last Edit: July 20, 2017, 11:49:36 am by molly »

tudi_x

  • Hero Member
  • *****
  • Posts: 532
Re: Learning Threads
« Reply #2 on: July 20, 2017, 11:53:04 am »
the compiler messages were about an inherited method.
i guess the override did the trick.
i cannot grasp yet why but i think it is about inheritance with the thread class.

thank you.

Code: Pascal  [Select][+][-]
  1.     procedure Execute; override;
  2.     destructor Destroy(); override;    
  3.  

i found the below interesting on this topic:

https://stackoverflow.com/questions/741735/what-is-the-meaning-of-the-reintroduce-and-override-directives-in-delphi#742112
« Last Edit: July 20, 2017, 12:03:12 pm by tudi_x »
Lazarus 2.0.2 64b on Debian LXDE 10

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Learning Threads
« Reply #3 on: July 20, 2017, 12:07:18 pm »
the compiler messages were about an inherited method.
i guess the override did the trick.
Please do not guess  :)

It was exactly why the compiler warned.

Read more on virtual keyword here.
Quote
virtual means that the method can be overwritten by the derived class. dynamic means the same thing, but the implementation differs: virtual members addresses are stored in a table, while dynamic members does not use tables and does not occupy RAM, but their resolution mechanism is slower.

A derived class can implement its own version of a virtual method, but the base method is still available; if the new method is marked override it hides the base virtual method, which cannot be called anymore. This holds true in case of dynamic methods, too. However you cannot override a method if it's not virtual or dynamic.

But... there is also another keyword in place for execute method, abstract, see here.

Quote
A method declared abstract is declared, but not implemented in the base class. Derived classes will be forced to provide their own implementation.

Quote
i cannot grasp yet why but i think it is about inheritance with the thread class.
The compiler not only thinks so, it knows so (and tries to warn you about your mistake)  :)

Not to belittle your experience, but are you sure you are up to programing threads ? That is a difficult enough topic even for those that are familair with basic concepts such as virtual/abstract methods. Perhaps it can help to read up on that a little first ?

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: Learning Threads
« Reply #4 on: July 20, 2017, 12:22:18 pm »
hi All,
basically i am learning about threads. as per my understanding the Execute method should run as soon as the thread is started.
please advise on the attached code as the writeln in the Execute method looks like does not run.

please also advise on the thread destructor. it looks like one block is stuck in heap. am i correctly freeing the thread?

thank you

Lazarus 1.8 RC3 on Windows 7 64bit
OK let me address a couple of your comments first as I go of to test the rest of your example.
Code: Pascal  [Select][+][-]
  1.     t := TTest.Create(True);
  2.     sleep(200);     //allow thread to be created and OS to switch to created thread
  3.  
not necessary. Create falls under a special condition, as long as the create methods is running the thread will never be started, after the create has exited the thread should be ready to run as is, except of course if you want to do more initialization. As for the "switch to created thread" as long as the thread is suspended it will never happen.

Code: Pascal  [Select][+][-]
  1.     t.Start;        //start thread
  2.     sleep(1000);    //allow thread to finish write
  3.     t.Destroy;
  4.     sleep(200);     //wrap up
  5.  
  6.     writeln('    ');
  7.     Terminate;
  8.   end;
  9.  
Now I'm assuming that the 1 second is enough for the thread to finish its job, a better approach would be to call terminate as you exit from the execute method and check to see if the terminated flag is up from the rest of your code, then wait for 25 or 50 miliseconds before destroying the thread.

At this point I'm assuming you have a reason to manually destroy the thread (some sort of pool mechanism running perhaps?) otherwise it would be better to set the FreeOnTerminate property to true. Now I'm of to study the rest of your code.
« Last Edit: July 20, 2017, 12:25:30 pm by taazz »
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

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Learning Threads
« Reply #5 on: July 20, 2017, 12:27:15 pm »
please also advise on the thread destructor. it looks like one block is stuck in heap. am i correctly freeing the thread?
Again, inheritance.

Now that you have overridden the destructor (i hope  ;D) you (still) need to call your ancestors destroy method, so that the ancestor class is able to neatly clean itself up and free allocated resources from memory.

Edit, more information on calling ancestor overridden methods can be found here.
« Last Edit: July 20, 2017, 12:34:21 pm by molly »

tudi_x

  • Hero Member
  • *****
  • Posts: 532
Re: Learning Threads
« Reply #6 on: July 20, 2017, 01:51:10 pm »
thank you both.
i am just learning about threads as i need to finish a production app where i do rest calls and it has a GUI.
definitely on belittle - when Pascal was taught in universities i was studying mechanical engineering.

i came about with the attached as i want to get a notification when the thread finished.
i could not answer for myself at two questions:
- why is the Ready function not called although i think i am calling it through the synchronize
- is the OnTerminate the standard way to go when you want to do an action in main thread on the created thread termination? how would you hook it up with a procedure in this case? i did not find how...
please help
Lazarus 2.0.2 64b on Debian LXDE 10

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Learning Threads
« Reply #7 on: July 20, 2017, 06:31:38 pm »
i am just learning about threads as i need to finish a production app where i do rest calls and it has a GUI.
definitely on belittle - when Pascal was taught in universities i was studying mechanical engineering.
No problem there, but i personally have a flaw that i am very allergic for people that just experiment and dump their 'shite' (pardon my phrasing) for review without knowing what their actually doing.

I understand that you are probably restricted by time, but imho the proper way is to start slowly, by reading documentation, understanding the topic and experiment with simple test-examples (which can be found all over the net, including fpc/lazarus examples, documentation and/or wiki pages).

But, that is my problem. not yours  :) (please forgive my rant)

Quote
- why is the Ready function not called although i think i am calling it through the synchronize
That is because the (started) thread is never terminated. You perhaps assume that calling application.terminate will automatically terminate your thread, but that is not the case.

fwiw: i used the OnTerminate event for my purpose, i'm not so much familiar with overloading DoTerminate.


edit:
Quote
- is the OnTerminate the standard way to go when you want to do an action in main thread on the created thread termination? how would you hook it up with a procedure in this case? i did
Yes, it is. But, your overloaded method (not event) gets stuck because you (eventually) destroy the thread from there. That is like shooting yourself in the foot  ;D

Code: Pascal  [Select][+][-]
  1.   TLinkApp = class(TCustomApplication)
  2.   private
  3.     t: TTest;
  4.     procedure MyTermination(Sender: TObject);  // The notify event procedure
  5.   protected
  6.     procedure DoRun; override;
  7.   public
  8.     constructor Create(TheOwner: TComponent); override;
  9.     destructor Destroy; override;
  10.   end;
  11.  
  12.   ...
  13.  
  14. procedure TLinkApp.DoRun;
  15. begin
  16.   writeln('main thread');
  17.  
  18.   writeln('creating thread');
  19.   t := TTest.Create(True);
  20.  
  21.   writeln('assign events');
  22.   t.OnTerminate := @MyTermination;
  23.    
  24.   writeln('start thread');
  25.   t.Start;        //start thread
  26.  
  27.   writeln('sleep for a second');
  28.   sleep(1000);
  29.   writeln('slept');
  30.  
  31.   writeln('terminating thread');
  32.   t.Terminate;  // <- that was missing from your original code
  33.   writeln('thread terminated');
  34.  
  35.   writeln('destroying thread');
  36.   t.Free;
  37.  
  38.   WriteLn('terminating application');
  39.   Terminate;
  40. end;
  41.  
  42. procedure TLinkApp.MyTermination(Sender: TObject);
  43. begin
  44.   WriteLn('Hello from termination');
  45. end;
  46.  

Note:
Code: Pascal  [Select][+][-]
  1.   procedure TLinkApp.FreeThread(AMessage: string);
  2.   begin
  3.     writeln(AMessage);
  4.     t.Destroy;  // <- you are destroying the thread from within the thread itself because this method is called from the thread itself.
  5.    // So, where goes the code from here ? t is not valid anymore.
  6.   end;
  7.  
« Last Edit: July 20, 2017, 07:59:38 pm by molly »

tudi_x

  • Hero Member
  • *****
  • Posts: 532
Re: Learning Threads
« Reply #8 on: July 20, 2017, 08:04:41 pm »
thank you molly.

sometime people are just having limited skills but i can tell you that the wiki could be improved to concentrate the additional info that is scattered all over and also have several flavors for the same example.

Lazarus 2.0.2 64b on Debian LXDE 10

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Learning Threads
« Reply #9 on: July 20, 2017, 08:43:50 pm »
@tudi_x:
I fully agree with you that the wiki is not always the best place to look and indeed information is scattered all over. I've worked on several open source projects that suffer the same 'limitation', so i am a bit used to it by now...

Also threads are not one of my strongest points so i would not advise myself to add to the wiki (otherwise i would).

Fortunately for me, i sometimes have the luxury to do what i advised in my earlier post and just 'toy' around.

As a small hint that you could perhaps apply inside your own code:
do many logging, showing you each and every handled event/method/etc, and you can learn a lot from that. It can be helpful, especially when dealing with threads and more so when using inheritance in general (sometimes events/calls happen in a complete different order than you imagined it would).

I've applied the same technique to debug your code (so basically doing your work :P), using writeln's to see where exactly your code got stuck, then looked for the cause, fix it, and run again (until i'm able to read back all my writeln's).

When you do this from the start of your development on a new subject, then you can 'build your way up'. When you add something new (and fail) it will show immediately. It's the simplest of debug techniques available that anyone should be able to apply and understand.

(in production code i used to use the same technique, but using debugout, turned off by default but can be activated in case of troubles, either manually or automated detection).

tudi_x

  • Hero Member
  • *****
  • Posts: 532
Re: Learning Threads
« Reply #10 on: July 21, 2017, 11:23:23 am »
thank you molly.
playing with threads would be rewarding now that 8 cores is something usual.
i have added writeln as per attached and also a time coordinate as per below
Code: Pascal  [Select][+][-]
  1. writeln('do terminate: ' + IntToStr(MilliSecondOfTheSecond(now)));
with the time i can check some sort of sequencing.
Lazarus 2.0.2 64b on Debian LXDE 10

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: Learning Threads
« Reply #11 on: July 21, 2017, 06:08:02 pm »
A word of warning though (as i haven't mentioned it yet... and your code is currently not really running into it):

Write and WriteLn routines are not thread-safe

For more information, you can read here.

So, we're going to try solve that  :)

One of the other things things i've noticed inside your last code is that you've time-stamped your  output: that is very good !

But, that could be done (automatically for us) using a much more elegant solution.

So, let's kill two birds with one stone and introduce a logging unit, see attachment.

Ive taken the liberty to 'convert' your posted project, making use of the logging unit so that you are able to see how you could use it.

In case you dislike using the unit, then that is ok. My aim is to show you how you could solve things more elegant.

Note that i had to go through some trouble because TCustomApplication has a procedure named log (for event logging). That is why you see the log calls in the customappplication being prefixed with the unit name.

A perhaps better/cleaner approach would be to add the log functions to your TCustomApplication, instead of using a separate unit.

I did not do so because the advantage of using a unit is that you can use it from wherever it is you need it (and not bound it to TCustomApplication).

Happy coding !  :D

 

TinyPortal © 2005-2018