Recent

Author Topic: MultiThreading  (Read 10880 times)

hac3ru

  • Full Member
  • ***
  • Posts: 113
MultiThreading
« on: August 14, 2013, 08:36:55 am »
I have an application that communicates with an external device via RS232. I want to create another Thread in order to process the data to be sent and the data that was received.
The question is: Does anyone have a good link where I could read about multithreading? With examples and so on? I read it on lazarus wiki but... It's not enough. I need to sync the main thread with the data one, I need to be able to block certain variables so they can be accessed only by 1 thread, so on and so forth.
I don't have any experience with multithreading so ....
Any help is greatly appreciated.

ludob

  • Hero Member
  • *****
  • Posts: 1173
Re: MultiThreading
« Reply #1 on: August 14, 2013, 08:54:11 am »
This is a good start: http://wiki.freepascal.org/Multithreaded_Application_Tutorial
It explains synchronize, critical sections and communication between threads. However it doesn't explain "so on and so forth"   ;)

hac3ru

  • Full Member
  • ***
  • Posts: 113
Re: MultiThreading
« Reply #2 on: August 14, 2013, 09:09:04 am »
Quote
I read it on lazarus wiki but..
Since I haven't worked with multi threading before, I don't even know how to sync between two processes... And wiki doesn't really explain it. It tells you that it is possible, it gives an example which I ran and does absolutely nothing as far as I can see....

I'm dieing to know how can I lock certain variables so they can be accessed by only one core (with an example if possible)
And how to signal to the main thread that the data thread has completed it's work... If I get this two in order, I should be able to start working with them, and deal with stuff as I get to it :)

//Got the code from wiki, modified it a bit and I got this:
Code: [Select]
unit Unit2;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils,Dialogs;

Type
  TMyThread = class(TThread)
  private
    countt:integer;
    count:integer;
    fStatusText : string;
    procedure ShowStatus;
    procedure ShowCount;

  private
    counter   : integer;
  protected
    procedure Execute; override;
  protected


  public
    function ShowCounter:integer;
    Constructor Create(CreateSuspended : boolean);
  end;

Implementation

uses unit1;

constructor TMyThread.Create(CreateSuspended : boolean);
begin
  counter:=0;
  FreeOnTerminate := True;
  inherited Create(CreateSuspended);
end;

procedure ShowCount;
begin
  countt := counter;
end;

function TMyThread.ShowCounter():integer;
begin
  result := counter;
end;

procedure TMyThread.ShowStatus;
// this method is executed by the mainthread and can thereforeaccess all GUI elements.
begin
  Form1.Label1.Caption := fStatusText;
end;

procedure TMyThread.Execute;
var
  newStatus : string;
begin
  fStatusText := 'TMyThread Starting...';
  Synchronize(@Showstatus);
  fStatusText := 'TMyThread Running...';
  while (not Terminated) do
    begin
      if counter>50 then
        counter:=0
      else
        counter:=counter+1;
      Synchronize(@ShowCount);
      if NewStatus <> fStatusText then
        begin
          newStatus := fStatusText;
          Synchronize(@Showstatus);
        end;
    end;
end;

end.
I want to show the counter value but using Synchronize. How can I do that? Right now, it's telling me
Code: [Select]
unit2.pas(44,10) Error: Identifier not found "countt"
unit2.pas(44,20) Error: Identifier not found "counter"
I want a function / process that will sync with the execution one, and return me the value of the counter as some other variable, so I can read it without any problems...

function TMyThread.ShowCounter():integer;  is working but it is not sync... I want to be sure that my function does not access the counter when the execution process is modifying it so ...

//Sorry for that. It was a newbie mistake. Found it and now it's working. The only thing I really want to know now, it how can I stop threads from accessing the same variables in the same time?
« Last Edit: August 14, 2013, 09:35:32 am by hac3ru »

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: MultiThreading
« Reply #3 on: August 14, 2013, 09:54:12 am »
1) You don't lock data to be accessed by a core but by a thread. which core executes the thread that has the variable locked is better left for the OS to handle. In order to lock data you need to decide the access method you need to use and then use the appropriate synchronization class for the job. The simplest method is the critical section which, to put it simple, implements a first come first served type of access aka FIFO list. In order to use it you create a single TCriticalSection for the data in question which will be used by all the threads that need access to that data. Before you access the data you call its Enter method and after you have finished working with the data you call its leave method. Here is a small unit that protects the access of an array of integers with a critical section internally making it thread safe.
Code: [Select]
unit Unit2;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, syncobjs;
type

  { TMyThreadSafeData }

  TMyThreadSafeData = class
  private
    FMyData : Array[0..100] of Integer;
    FMyLock : TCriticalSection;
    function GetInt(Index: Integer): Integer;
    procedure SetInt(Index: Integer; AValue: Integer);
  protected
    procedure InitArray;
  public
    constructor Create;
    destructor Destroy; override;
    property MyInt[Index:Integer]:Integer read GetInt write SetInt;
  end;

implementation

{ TMyThreadSafeData }

function TMyThreadSafeData.GetInt(Index: Integer): Integer;
begin
  FMyLock.Enter;
  try
    if (Index <0) or (Index > 100) then raise Exception.Create('Indexout of Bounds(0..100) : '+IntToStr(Index));
    Result := FMyData[Index];
  finally
    FMyLock.Leave;
  end;
end;

procedure TMyThreadSafeData.SetInt(Index: Integer; AValue: Integer);
begin
  FMyLock.Enter;
  try
    if (Index <0) or (Index > 100) then raise Exception.Create('Indexout of Bounds(0..100) : '+IntToStr(Index));
    FMyData[Index] := AValue;
  finally
    FMyLock.Leave;
  end;
end;

procedure TMyThreadSafeData.InitArray;
var
  vCntr: Integer;
begin
  for vCntr := 0 to 100 do
    FMyData[vCntr] := 0;
end;

constructor TMyThreadSafeData.Create;
begin
  inherited Create;
  FMyLock := TCriticalSection.Create;
  FMyLock.Enter;
  try
    InitArray;
  finally
    FMyLock.Leave;
  end;
end;

destructor TMyThreadSafeData.Destroy;
begin
  FMyLock.Free;
  inherited Destroy;
end;

end.
As you can see any thread trying to access the data will automatically call the enter method of the critical section. the first thread that it calls it will place a lock on all other threads trying to get access to the data until the first thread has finished and called leave. Keep in mind that if for any reason the first thread calls enter multiple times it will be granted access with out waiting and a counter will keep track how many times the thread has requested a lock and when that counter reaches zero then and only then the other threads will be able to get access so it is crucial that each enter call must be followed by a leave call regardless of what happened during the execution that's why I enclose the call to leave in a try finally construct.

2)signalling the main thread that a thread has finished processing there are a number of methods which you can
  a) use the onTerminate event of a TThread class and when the main TThread.execute method exits it will automatically call that event from inside the main threads execution loop.
  b) use the application.QueueAsyncCall method to add a method call inside the applications main execution loop.
  c) synchro classes like events and semaphores can be used and pull their status from the main thread.

EDIT:

Well since the implementation of procedure ShowCount is not declared as part of the thread class the compiler is correct to not being able to access those variables change the declaration to
procedure TMyThread.ShowCount;
« Last Edit: August 14, 2013, 09:59:21 am 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

hac3ru

  • Full Member
  • ***
  • Posts: 113
Re: MultiThreading
« Reply #4 on: August 14, 2013, 11:53:38 am »
Can I create a Critical section and use the variables from other class in it?

If i have
index1, index2, index3 in class INDEX let's say... Can I create a Critical Section to lock the class INDEX or the variables must be inside the critical section? Thanks.

So.. If I the OnTerminate(writedata) and writedata is a process, it will call the writedata process after the thread has finished working right? :)

And...
Code: [Select]
if TrackBar_OnOff.Position = 0 then
      begin
        MData.mesaj := '0';
        Showmessage('0');
      end
      else
      begin
        Showmessage('0');
        MData.mesaj := '1';
      end;     
This doesn't work. WHY?
I do have a trackbar named TrackBar_OnOff
« Last Edit: August 14, 2013, 11:59:14 am by hac3ru »

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: MultiThreading
« Reply #5 on: August 14, 2013, 12:41:32 pm »
Can I create a Critical section and use the variables from other class in it?

In what exactly?


If i have
index1, index2, index3 in class INDEX let's say... Can I create a Critical Section to lock the class INDEX or the variables must be inside the critical section? Thanks.

I have already given you the code of a class that protects its own data, as long as your class properties all have both getter and setter method, no direct access to the internal fields and both getter and setter method call the enter/leave pair of methods as demonstrated then you are ok.



So.. If I the OnTerminate(writedata) and writedata is a process, it will call the writedata process after the thread has finished working right? :)
No, Onterminate is a "class event" (lets call it as such to distinguish it from the TEvent class), you need to declared a handler exactly as it is declared you can't give it what ever parameters you like. for a starter declare a method on the form name MyTerminatorHandler or anything along the lines making sure it has the correct declaration then when you create the Tmythread you create with the Suspended parameter set to true and attach the handler to the event. eg
Code: [Select]
Var
  vTmpthread :TMythread;
begin
   vTmpThread := TmyThread.Create(true);
   vTmpThread.OnTerminate := @MyTerminatorHandler;
end;
Remeber that this will work if the code above is part of a method of the form that you declared the MyTerminatorHandler method.


Look at the source code for more info on how to declare the event.

Now as I have already said the onterminate event calls the event handler you have attach to it from the main application thread using synchronize so if you attach a handler declared in an other thread then that code needs to take in to account that it will be called from an other thread and protect any data access appropriately. Only one event can be attached to the event.


And...
Code: [Select]
if TrackBar_OnOff.Position = 0 then
      begin
        MData.mesaj := '0';
        Showmessage('0');
      end
      else
      begin
        Showmessage('0');
        MData.mesaj := '1';
      end;     
This doesn't work. WHY?
I do have a trackbar named TrackBar_OnOff

Not enough information are given, where is the trackbar declared where is the code implemented are all on the same object eg the TForm1 or the code shown is part of a class eg a TMyThread descendant and the trackbar is part of an other class eg TForm1? In any case what ever you place on a form is not accessible outside the form you need to reference the variable that holds the form eg form1.Trackbar_onOff.position, outside the methods of the form.
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

hac3ru

  • Full Member
  • ***
  • Posts: 113
Re: MultiThreading
« Reply #6 on: August 14, 2013, 01:13:02 pm »
Lots of thanks, Taazz  :)

Okay I know where the problem comes from but I can't fix it:
I got a Trackbar on form1. When changing the trackbar OFF/ON (0/1) something is executed. The problem is that the function was executing twice when I was changing the TrackBar.Position. I said, I'll add a public variable named TBar1 which will take the value on form create
Code: [Select]
Tbar1:=TrackBar_OnOff.Position

This Tbar1 will hold the last value of the Trackbar.Position so if Tbar1 and Trackbar Position are the same, the code does not execute=>
Code: [Select]
procedure TForm1.TrackBar_OnOffChange(Sender: TObject);
begin 
if Tbar1 <> TrackBar_OnOff.Position then
 if TrackBar_OnOff.Position = 0 then
      begin
        MData.mesaj := '0';
        Showmessage('At 0');
      end
      else
      begin
        MData.mesaj := '1';
        Showmessage('At 1');
      end; 
Tbar1:=Trackbar_OnOff.Position;   
end;
The problem is that, it still executes twice and for some reason it doesn't pass the first if =>
Code: [Select]
if Tbar1 <> TrackBar_Mode.Position thenThis is all the code. Nothing more to it. The threads will be added later... I want a trackbar which turns ON/OFF but using trackbar as is, it will send the same command twice...

Got it fixed... Sort of.... It's still sending two messages... Any ideas?

Another question: How can I sync data with the main Thread? I have this procedure in the second thread:
Code: [Select]
procedure TMyThread.SyncData;
begin
  Form1.ID:=ID;
  Form1.OP:=OP;
  Form1.Addr:=Addr;
  Form1.DL:=DL;
  Form1.Msg:=Msg;
  Form1.Chk:=Chk;
  Form1.Calc_chk:=calc_chk;
  Form1.ack:=ack;
end; 
I call this at the last line of the secondary thread. Still, it's not doing it ...

And I tried to read on the event handlers but nothing's cleared... Can you provide a link or something where I could read about them? As I said, I'm new to this ...  :-[

I want to process data that gets from the RS232 interface into a new thread. I have the LazSerial defined in Form1 and I tried to do
Code: [Select]
procedure TForm1.LS1RxData(Sender: TObject);
var
  receivedbyte: byte;              //The variable which stores the received byte
begin
  receivedbyte:=LS1.Readbyte;
  Showmessage('Rec_app='+IntToStr(receivedbyte));
  MThread.Run(receivedbyte); 
In MThread.Run(receivedbyte) there is:
Code: [Select]
procedure TMyThread.Run(receivedbyte:byte);
begin
Showmessage('In Thread='+IntToStr(receivedbyte));
if (receivedbyte = StrToInt('0x' + ff)) then
    begin
      if (not MRec.write_msg(receivedbyte, Count, ID, OP, DL, CHK, Addr, Msg)) then
        ShowMessage('Could not write to message.' + sLineBreak +
          'Counter=' + IntToStr(Count));
      Count := Count + 1;            //Count is the counter of bytes received.
      alt := receivedbyte;           //Alt is used to store the last received Byte
      Showmessage('Byte is equal to 255');
    end;
end
Still, there is a problem. I only get the 1st byte. Not the rest of them... At the first byte it all works great. After that, total silence...
« Last Edit: August 14, 2013, 02:51:19 pm by hac3ru »

hac3ru

  • Full Member
  • ***
  • Posts: 113
Re: MultiThreading
« Reply #7 on: August 14, 2013, 04:42:28 pm »
Okay got everything working, except the FinishedWork signaling... Can anyone help me? :)

snorkel

  • Hero Member
  • *****
  • Posts: 817
Re: MultiThreading
« Reply #8 on: August 16, 2013, 05:42:15 pm »
Okay got everything working, except the FinishedWork signaling... Can anyone help me? :)

Can't you use LCL messages to to that, they work great from threads and work on all platforms.
When running on windows it uses windows messages and compatible routines when run on other platforms.

See:  http://lazarus-dev.blogspot.com/2008/01/new-0926-features-part-1-sendmessage.html

Quote from blog:

"After releasing 0.9.24 we reevaluated our decision since gtk and qt supported PostMessage and SendMessage. Then an implementation for Carbon appeared. Of course it would be a joke to block messages under Windows when all other widgetsets (where messages are aliens) support them. Now you can use PostMessage and SendMessage with few limitations:
- use Message numbers >= LM_USER
- sending messages outside application does not work (you cannot send message to another application)"
***Snorkel***
If I forget, I always use the latest stable 32bit version of Lazarus and FPC. At the time of this signature that is Laz 3.0RC2 and FPC 3.2.2
OS: Windows 10 64 bit

sam707

  • Guest
Re: MultiThreading
« Reply #9 on: August 16, 2013, 10:56:57 pm »

hac3ru

  • Full Member
  • ***
  • Posts: 113
Re: MultiThreading
« Reply #10 on: August 19, 2013, 08:57:39 am »
Okay got a problem:

I want, each time a byte is received on the serial interface, the new thread to deal with it. Can anyone help me?

I did
Code: [Select]
procedure TMyThread.Execute;
var receivedbyte:byte;
begin
    receivedbyte := Form1.LS1.ReadByte;
end;
This, I call OnRxData. Still, it's not getting only the 1st byte. After that, silence....

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: MultiThreading
« Reply #11 on: August 19, 2013, 09:14:19 am »
Okay got a problem:

I want, each time a byte is received on the serial interface, the new thread to deal with it. Can anyone help me?

I did
Code: [Select]
procedure TMyThread.Execute;
var receivedbyte:byte;
begin
    receivedbyte := Form1.LS1.ReadByte;
end;
This, I call OnRxData. Still, it's not getting only the 1st byte. After that, silence....

I have no idea how you call it and from where but if you call it instead of the thread you misunderstood something crucial for multithreading.
in any case try the following.

Code: [Select]
procedure TMyThread.Execute;
var receivedbyte:byte;
begin
    Repeat
      receivedbyte := Form1.LS1.ReadByte;
    until terminated;
end;
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

hac3ru

  • Full Member
  • ***
  • Posts: 113
Re: MultiThreading
« Reply #12 on: August 19, 2013, 09:39:13 am »
I start the thread...
Anyway. How can I terminate it then?
Code: [Select]

procedure TForm1.LS1RxData(Sender: TObject);
begin
  MThread.Start;
end;
That's what I meant :)
Trying what you said. Thank you

Not getting anything this time. Not even the 1st char...
« Last Edit: August 19, 2013, 09:43:38 am by hac3ru »

taazz

  • Hero Member
  • *****
  • Posts: 5368
Re: MultiThreading
« Reply #13 on: August 19, 2013, 09:47:23 am »
mthread.terminate wil stop the loop from running and the thread as well. after that I think you need to free the thread and recreate it in order to be able to run it again.
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

hac3ru

  • Full Member
  • ***
  • Posts: 113
Re: MultiThreading
« Reply #14 on: August 19, 2013, 09:55:07 am »
I did something and got it working... I'm processing the message and it's all working now. I need to signal the main thread when work's done so the main will show the actual received message and app ready to go :)

I have another question though (you must be sick of me :lol: )
How can I force a trackbar to "send" the position change only once?
Do this for me:

Create a form.
Add a trackbar with Min=0 and Max=1
Add:
Code: [Select]
procedure TForm1.Trackbar1Change(Sender: TObject);
begin
showmessage(IntToStr(Trackbar1.Position));
end;
You will see that you get two messages, for only 1 trackbar position change. How can I stop getting two messages? Any idea?
« Last Edit: August 19, 2013, 09:57:10 am by hac3ru »

 

TinyPortal © 2005-2018