Recent

Author Topic: multithreading learning curve  (Read 21800 times)

user5

  • Sr. Member
  • ****
  • Posts: 419
multithreading learning curve
« on: August 12, 2015, 08:33:18 pm »
This post is the result of a graphics thread that I was involved with (Re: JPG fast loading) and I put it here since it's about stuff other than just graphics. I've been learning and studying this forum's archives on multithreading and I hope some of you guys will look at the crude test code below to see if you think it's okay. It may not be quite right but it does run. Is the way I passed a variable from the main form (main thread) to MyThread ok? This is my first multithreading code attempt. Thanx.

Code: [Select]
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  wincrt,intfgraphics, windows, SysUtils, FileUtil, Forms,
  Controls, Graphics, LResources, Process, ExtCtrls, Dialogs, StdCtrls, LCLProc,
  ActnList, LCLIntf, LCLType, Interfaces, Buttons, ExtDlgs, Menus, ComCtrls,
  strutils, MMSystem, Classes, GraphType;

type

  TMyThread = class(TThread)
    private
    protected
      procedure Execute; override;
    public
     //tempstr1:string;
    end;

  TMyNewThread = class(TThread)
    private
    protected
      procedure Execute; override;
    public
    end;

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    Image2: TImage;
    Image3: TImage;
    Shape1: TShape;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
    tempstr1:string;
  end;

var
  Form1: TForm1;

implementation

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var MyThread : TMyThread;
    MyNewThread:TMyNewThread;
    AJpg3: TJpegImage;
begin
 MyThread := TMyThread.Create(True);
 MyNewThread := TMyNewThread.create(true);
 MyThread.Start;
 MyNewThread.start;
 AJpg3 := TJpegImage.Create;
 AJpg3.LoadFromFile('c:\temp39\pix3.jpg');
 Form1.Image3.picture.Bitmap.Assign(AJpg3);
 AJpg3.free;
 application.processmessages;
 MyThread.Destroy;
 MyNewThread.Destroy;
end;

{$R *.lfm}


procedure TMyThread.Execute;
var AJpg1: TJpegImage;
begin
 AJpg1 := TJpegImage.Create;
 AJpg1.LoadFromFile('c:\temp39\pix1.jpg');
 Form1.Image1.picture.Bitmap.Assign(AJpg1);
 AJpg1.free;
end;


 procedure TMyNewThread.Execute;
 var AJpg2: TJpegImage;
  begin
   AJpg2 := TJpegImage.Create;
   AJpg2.LoadFromFile('c:\temp39\pix2.jpg');
   Form1.Image2.picture.Bitmap.Assign(AJpg2);
   AJpg2.free;
  end;


end.

derek.john.evan, thanks mucho for your help on the graphics stuff. Multithreading might make the program be a couple of microseconds faster during the capture process. Regarding the appearance of the program's windows which you mentioned, I know that they have a plain, utilitarian appearance but it's not a priority right now. The most important goal is for the program to produce perfect quality videos. Hope you liked the cartoon movie that I attached which was made with the program. The program created that from just a text file and a soundtrack.

derek.john.evans

  • Guest
Re: multithreading learning curve
« Reply #1 on: August 12, 2015, 08:48:29 pm »
Yep, a good start.
Just make sure you wrap your code in try/finally blocks. If LoadFromFile fails, it will skip the TJPEGImage.Free and you will have a memory leak.

Plus, you cant access the GUI using just:
Code: [Select]
Form1.Image2.picture.Bitmap.Assign(AJpg2);
That code needs to be put into a method, and called with TThread.Synchronize(). So, the jpeg pointers need to be in the TThread object, so other methods can access them.

I dont think threading speeds anything up. It simply gives the app the ability to use spare CPU time. So, I'd use it for a JPEG read ahead thread. How far ahead it reads depends on the processor and how big the JPEG's are.

So, the idea is, if JPEG's are loading quickly, then the buffer will be filled with future data. But, if something big comes along, then the look ahead buffer will shrink.

Eventually, if the threads arn't reading fast enough, then yes, you will chug out.

derek.john.evans

  • Guest
Re: multithreading learning curve
« Reply #2 on: August 12, 2015, 08:50:56 pm »
Just a quick question. How are you drawing the final image to the screen? Do you have a single TBitmap backbuffer?

user5

  • Sr. Member
  • ****
  • Posts: 419
Re: multithreading learning curve
« Reply #3 on: August 12, 2015, 09:55:53 pm »
I didn't mean that multithreading would speed up the processing. I meant that it would speed up the overall process because it would load up to seven TImages at basically the same time instead (parallel) of one after another(linear).

I don't understand why you say "Form1.Image2.picture.Bitmap.Assign(AJpg2);" won't load the jpg into Image2. The test program using that code line loads the TImage just fine.

I know that Synchronize(@something) accesses the main form components but I don't know how to use it yet. I've heard some folks say that it's dangerous to use it.

Could you give me an example of how to use Synchronize in terms of my posted code?

I will certainly use the "try" statement.

As far as your question is concerned, I'm not sure what you mean by "final image." In this case I'm not drawing on the form canvas. The program loads all the pertinent jpgs into TImages for display and then makes a sized screen capture of those displayed TImages into a bitmap that with other captured bitmaps is joined into a movie. Each bitmap becomes one frame of the movie under construction. The program offers the user a choice of a standard screen capture method or an HDC capture method. The standard method is slightly slower but makes smaller bmps.

derek.john.evans

  • Guest
Re: multithreading learning curve
« Reply #4 on: August 12, 2015, 10:04:58 pm »
Not using Synchronize is dangerous.
Threads sharing memory is fine until one of them changes the memory.

The GUI (and OS) access information in Form1.Image2.picture.Bitmap to display the bitmap.

If the bitmap is being displayed, while you assign a new bitmap to it, then the GUI will crash. Your tests wont show the effect, but if you had a TImage stretched to a TForm, and you were updaing it with new JPEG's, the app will crash. Especially if the form is being resized, or parts are being shown because windows are moved over the top of it.


user5

  • Sr. Member
  • ****
  • Posts: 419
Re: multithreading learning curve
« Reply #5 on: August 13, 2015, 07:22:05 pm »
derek.john.evan, I've been doing some more studying and learning and below is my updated code. Please note that I heeded your warning and that I'm now using Synchronize(). I hope that it's as fast as it is safe-According to http://wiki.freepascal.org/Multithreaded_Application_Tutorial:
"When you call that method through Synchronize(@MyMethod), the thread execution will be PAUSED, the code of MyMethod will be called from the main thread, and then the thread execution will be RESUMED." I know that most folks use Synchronize(@) in threads, though.

That new Lazarus Initiative is interesting and it sure stirred things up. I wonder what it portends. For my part no one knows how grateful I am that Lazarus exists. Thanks and I'm sure that I'll see ya around.

Code: [Select]
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  wincrt,intfgraphics, windows, SysUtils, FileUtil, Forms,
  Controls, Graphics, LResources, Process, ExtCtrls, Dialogs, StdCtrls, LCLProc,
  ActnList, LCLIntf, LCLType, Interfaces, Buttons, ExtDlgs, Menus, ComCtrls,
  strutils, MMSystem, Classes, GraphType;

type

  TMyThread = class(TThread)
    private
      procedure LoadTImage1;
    protected
      procedure Execute; override;
    public
      AJpg1: TJpegImage;
    end;

  TMyNewThread = class(TThread)
    private
      procedure LoadTImage2;
    protected
      procedure Execute; override;
    public
      AJpg2:TJpegImage;
    end;

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    Image2: TImage;
    Image3: TImage;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
    tempstr1:string;
  end;

var
  Form1: TForm1;

implementation

procedure TForm1.Button1Click(Sender: TObject);
var MyThread : TMyThread;
    MyNewThread:TMyNewThread;
    AJpg3: TJpegImage;
begin
 MyThread := TMyThread.Create(True);
 MyNewThread := TMyNewThread.create(true);
 MyThread.FreeOnTerminate := true;
 MyThread.Start;
 MyNewThread.FreeOnTerminate := true;
 MyNewThread.start;
 AJpg3 := TJpegImage.Create;
 AJpg3.LoadFromFile('c:\temp39\pix3.jpg');
 Form1.Image3.picture.Bitmap.Assign(AJpg3);
 AJpg3.free;
 application.processmessages;
end;

{ TForm1 }





{$R *.lfm}

procedure TMyThread.LoadTImage1;
begin
 Form1.Image1.picture.Bitmap.Assign(AJpg1);
end;

procedure TMyNewThread.LoadTImage2;
begin
 Form1.Image2.picture.Bitmap.Assign(AJpg2);
end;


procedure TMyThread.Execute;
begin
 AJpg1 := TJpegImage.Create;
 AJpg1.LoadFromFile('c:\temp39\pix1.jpg');
 Synchronize(@LoadTImage1);
 AJpg1.free;
end;


 procedure TMyNewThread.Execute;
  begin
   AJpg2 := TJpegImage.Create;
   AJpg2.LoadFromFile('c:\temp39\pix2.jpg');
   Synchronize(@LoadTImage2);
   AJpg2.free;
  end;

end.


User137

  • Hero Member
  • *****
  • Posts: 1791
    • Nxpascal home
Re: multithreading learning curve
« Reply #6 on: August 13, 2015, 09:49:33 pm »
I did heavy changes, but i hope you can see what i did there:
Code: [Select]
unit Unit1;

{$mode objfpc}{$H+}

interface

uses
  wincrt,intfgraphics, windows, SysUtils, FileUtil, Forms,
  Controls, Graphics, LResources, Process, ExtCtrls, Dialogs, StdCtrls, LCLProc,
  ActnList, LCLIntf, LCLType, Interfaces, Buttons, ExtDlgs, Menus, ComCtrls,
  strutils, MMSystem, Classes, GraphType;

type

  { TMyThread }

  TMyThread = class(TThread)
  protected
    procedure Execute; override;
  public
    FImage: TImage;
    FFilename: string;
    constructor Create(image: TImage; filename: string);
  end;

  { TForm1 }

  TForm1 = class(TForm)
    Button1: TButton;
    Image1: TImage;
    Image2: TImage;
    Image3: TImage;
    procedure Button1Click(Sender: TObject);
  private
    { private declarations }
  public
    { public declarations }
    //tempstr1:string;
  end;

var
  Form1: TForm1;

implementation

procedure TForm1.Button1Click(Sender: TObject);
begin
  TMyThread.Create(image1, 'c:\temp39\pix1.jpg');
  TMyThread.Create(image2, 'c:\temp39\pix2.jpg');
  TMyThread.Create(image3, 'c:\temp39\pix3.jpg');
end;

{ TForm1 }

{$R *.lfm}

constructor TMyThread.Create(image: TImage; filename: string);
begin
  inherited Create(false);
  FreeOnTerminate:=true;
  FImage:=image;
  FFilename:=filename;
end;

procedure TMyThread.Execute;
var AJpg: TJpegImage;
begin
  AJpg := TJpegImage.Create;
  AJpg.LoadFromFile(FFilename);
  FImage.picture.Bitmap.Assign(AJpg);
  AJpg.free;
end;

end.
I didn't go as far as run it with pictures, but it compiles. You don't need synchronize in this case, with threads that are strictly limited to their own spaces. You will never have a case where 2 threads load same image at the time, unless you do that on purpose.

Well, synchronize is easy to add to this. Move AJpg to thread variables and re-create the procedure LoadImage; you had before. Bigger point is that you don't need to make a new thread class for each image, but generalize it to 1.
« Last Edit: August 14, 2015, 11:33:22 am by User137 »

coliv_aja

  • New Member
  • *
  • Posts: 38
Re: multithreading learning curve
« Reply #7 on: August 14, 2015, 04:01:56 am »
Did you just freing the thread manually after you starting it?
That's not safe, or not right at all. You don't know how long your thread will run. You can wait for it with TThread.WaitFor.
Or better just let it run and end by itself. You can set FreeOnterminate on Create event. And you should use synchronize when updating the ui.

Code: [Select]
TMyThread = class(TThread)
public
  constructor Create;
end;

constructor TMyThread.Create;
begin
  inherited Create(True);
  FreeOnTerminate := True;
end;     

Code: [Select]
procedure TForm1.Button1Click(Sender: TObject);
begin
  with TMyThread.Create do begin
    ...
    Start;
  end;
end;

---
Sorry, didn't read your last post.
But you still have to use synchronize, because image1, image2, image3 occupied by mainthread(main ui), you just passing the pointer.

Code: [Select]
TMyThread = class(TThread)
private
  AJpg: TJpegImage;
  procedure UpdateImage;
...
end;
...
procedure UpdateImage;
begin
  if Assigned(AJpg) then
    FImage.picture.Bitmap.Assign(AJpg);
end;

procedure TMyThread.Execute;
var AJpg: TJpegImage;
begin
  AJpg := TJpegImage.Create;
  try
    AJpg.LoadFromFile(FFilename);
    Synchonize(@UpdateImage);
  finally
    AJpg.free;
  free;
end;
« Last Edit: August 14, 2015, 04:18:36 am by coliv_aja »

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: multithreading learning curve
« Reply #8 on: August 14, 2015, 04:08:00 am »
Did you just freing the thread manually after you starting it?
That's not safe, or not right at all. You don't know how long your thread will run. You can wait for it with TThread.WaitFor.

FYI, TThread.Free leads to TThread.WaitFor.

And you should use synchronize when updating the ui.

Why?

coliv_aja

  • New Member
  • *
  • Posts: 38
Re: multithreading learning curve
« Reply #9 on: August 14, 2015, 04:27:19 am »
FYI, TThread.Free leads to TThread.WaitFor.

yes, I just suggest better let it run by itself and not blocking the ui.

Why?
Because he is trying to change the image in mainform.

engkin

  • Hero Member
  • *****
  • Posts: 3112
Re: multithreading learning curve
« Reply #10 on: August 14, 2015, 04:50:59 am »
Why?
Because he is trying to change the image in mainform.

What's wrong with that?

coliv_aja

  • New Member
  • *
  • Posts: 38
Re: multithreading learning curve
« Reply #11 on: August 14, 2015, 05:31:48 am »
Why?
Because he is trying to change the image in mainform.

What's wrong with that?

To make sure it was thread-safe. In case there is more thread that want to change the same object at the same time. Isn't it the best practice to let the ui changes related in main thread

balazsszekely

  • Guest
Re: multithreading learning curve
« Reply #12 on: August 14, 2015, 07:21:03 am »
@engkin
Updating visual controls from inside a thread, without synchronization is notoriously unsafe. By default, LCL is not thread-safe and expects to be used only from the main thread. Sometimes your code may seems to work without synchronization, but usually wont. Not using synchronize will block your main thread or lead to multi-threading conflicts.

PS:
The synchronize method is not perfect either, it will cause the thread to wait until the synchronization is done. There are better ways...depending on platform you're using.

JD

  • Hero Member
  • *****
  • Posts: 1913
Re: multithreading learning curve
« Reply #13 on: August 14, 2015, 11:08:21 am »
PS:
The synchronize method is not perfect either, it will cause the thread to wait until the synchronization is done. There are better ways...depending on platform you're using.

True. SendMessage and PostMessage on Windows. But what is the equivalent on Linux? I would love to find out.

JD
Linux Mint - Lazarus 4.6/FPC 3.2.2,
Windows - Lazarus 4.6/FPC 3.2.2

mORMot 2, PostgreSQL & MariaDB.

user5

  • Sr. Member
  • ****
  • Posts: 419
Re: multithreading learning curve
« Reply #14 on: August 14, 2015, 12:42:42 pm »
Thanks so much for your brilliant and helpful responses. I know that most folks endorse Synchronize(@), but not everyone. Regarding the freeing of the threads in my first example, I did something like "MyThread.Destroy" manually but it was my understanding that using "while (not Terminated)..." or "MyThread.FreeOnTerminate := true;" would automatically cause the thread to free itself when it finishes.

Big question: Does the latter boolean statement have to be used in a "constructor" method to be effective?

I'm intently studying everything that you guys suggested, especially the code that user137 posted, which is very similar to my first posted threading code and if that code works correctly time after time without crashing then maybe it's okay.

The reason for using threads in this case is to possibly gain a few microseconds of time in loading the jpgs but even without threads my current process is already pretty fast so I sure don't want to use anything that has a pause in it. In that case it would be better to leave the code like it is rather than slow it down by a few microseconds.

Currently, the jpg loading is in a loop that displays all pertinent images, captures them into a movie frame and then repeats that process until the movie is finished. During playback, though, those images are displayed but not captured and for playback there is a delay in the loop that basically starts out equalling the project frame rate.

If the movie being made and thus the playback rate is at 30 fps, for example, the delay starts out at .033. As the loop runs during playback, a short but elegantly beautiful bit of code regularly checks to see if the correct number of frames have been displayed after a given amount of time. If the number of frames shown is too little or too much then the program changes the delay accordingly.

If program has to, it can reduce the delay until it equals zero, if necessary, but even at zero the images still play at the correct rate! Of course, this particular process is load sensitive so if the delay is reduced to zero but the computer processor is still bogged down then the playback will unfortunately be a bit slow. All praise et al...

 

TinyPortal © 2005-2018