I'm user of delphi 2010, my current machine is intel core i7, running windows 7 x64. I've write the following codes:
type
TForm1 = class(TForm)
Memo1: TMemo;
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
FCount: Integer;
FTickCount: Cardinal;
procedure DoTest;
procedure OnTerminate(Sender: TObject);
end;
TMyThread = class(TThread)
private
FMethod: TProc;
protected
procedure Execute; override;
public
constructor Create(const aCreateSuspended: Boolean; const aMethod: TProc);
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
var i: integer;
T1, T2: Cardinal;
begin
T1 := GetTickCount;
for i := 0 to 9 do
DoTest;
T2 := GetTickCount;
Memo1.Lines.Add(Format('no thread=%4f', [(T2 - T1)/1000]));
end;
procedure TForm1.Button2Click(Sender: TObject);
var T: TMyThread;
i: integer;
begin
FCount := 0;
FTickCount := GetTickCount;
for i := 0 to 9 do begin
T := TMyThread.Create(True, DoTest);
T.OnTerminate := OnTerminate;
T.Priority := tpTimeCritical;
if SetThreadAffinityMask(T.Handle, 1 shl (i mod 8)) = 0 then
raise Exception.Create(IntToStr(GetLastError));
Inc(FCount);
T.Start;
end;
end;
procedure TForm1.DoTest;
var i: integer;
begin
for i := 1 to 10000000 do
IntToStr(i);
end;
procedure TForm1.OnTerminate(Sender: TObject);
begin
Dec(FCount);
if FCount = 0 then
Memo1.Lines.Add(Format('thread=%4f', [(GetTickCount - FTickCount)/1000]));
end;
constructor TMyThread.Create(const aCreateSuspended: Boolean; const aMethod:
TProc);
begin
inherited Create(aCreateSuspended);
FMethod := aMethod;
FreeOnTerminate := True;
end;
procedure TMyThread.Execute;
begin
FMethod;
end;
Click on Button1 will shows 12.25 seconds, while Button2 will shows 12.14 seconds. My problem is why i cannot get more obvious difference of time taken (less than 10 seconds) although i'm running parallel threads ?
Memory allocation seems to be the main problem here.
If you replace the payload with
procedure TForm6.DoTest;
var i: integer;
a: double;
begin
a := 0;
for i := 1 to 10000000 do
a := Cos(a);
end;
the code will parallelize nicely indicating that there's no real problem with your framework.
If you, however, replace the payload with memory allocation/deallocation
procedure TForm6.DoTest;
var i: integer;
p: pointer;
begin
for i := 1 to 10000000 do begin
GetMem(p, 10);
FreeMem(p);
end;
end;
the parallel version will run much slower than the single-threaded one.
When calling IntToStr, a temporary string is allocated and destroyed and this allocations/deallocations are creating the bottleneck.
BTW1: Unless you really really know what you're doing, I'm strongly advising against running threads at tpTimeCritical priority. Even if you really really know what you're doing you shouldn't be doing that.
BTW2: Unless you really really know what you're doing, you should not mess with affinity masks on thread level. System is smart enough to schedule threads nicely.
If you have memory intensive threads (many memory allocations/deallocations) you better use TopMM instead of FastMM: http://www.topsoftwaresite.nl/
FastMM uses a lock which blocks all other threads, TopMM does not so it scales much better on multi cores/cpus!
I'm not 100% sure, but there's a chance that the OnTerminate event is called from the context of the TThread. If that's the case (I must admit I haven't checked this), you'd be better off using InterlockedDecrement on FCount, and synchronizing the GUI updates. Just a minor point, but in production code these things matter.
Related
So I came across some code similar to this:
aThread := TmyThread.Create(param1, param2);
h := aThread.handle;
repeat
if (MsgWaitForMultipleObjects(1, h, False, INFINITE, QS_ALLINPUT) = WAIT_OBJECT_0)
then break;
Application.ProcessMessages
until False;
aThread.Free;
myProgressBar.Progress := myProgressBar.Max; //an internal component, not really important here
Which, I assume, is meant to provide a way of updating the GUI so it doesn't appear blocked, but also allow for the end process GUI to be updated (the progress bar), while some long task is taking place.
But it contains the dreaded Application.ProcessMessages.
I've read The Darkside of Application.ProcessMessages and many other Delphi blogs suggesting it's time to use a new thread instead when Application.ProcessMessages is being used.
So, is it wise to phase out this method of keeping the main/GUI threaded idle for something like the AnonymousThread approach shown here? Or something else?
This Noob is confused as to why it's suggested that a process that calls Application.ProcessMessages is a good candidate for a Thread, but the thread in question relies on the very thing we're being told not to do!
The main idea is not to wait for the thread. Thread should inform your form when it finished. In other words the code which should be executed after the the thread is finished should be isolated to a separate procedure (see TForm1.ThreadCompletedHandler) and thread should call it after it is finished.
Here is a small sample:
unit Unit1;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ComCtrls;
type
TmyThread = class(TThread)
private
FParam1, FParam2: Integer;
FUpdateProc: TProc<TmyThread, Integer>;
FCompleteProc: TProc<TmyThread>;
procedure SyncCompleteProc;
procedure QueueUpdateProc(APosition: Integer);
protected
procedure Execute; override;
public
constructor Create(AParam1, AParam2: Integer;
AUpdateProc: TProc<TmyThread, Integer>;
ACompleteProc: TProc<TmyThread>);
end;
TForm1 = class(TForm)
Button1: TButton;
myProgressBar: TProgressBar;
procedure Button1Click(Sender: TObject);
private
FThread: TThread;
procedure ThreadUpdateHandler(AThread: TMyThread; APosition: Integer);
procedure ThreadCompletedHandler(AThread: TMyThread);
protected
procedure UpdateActions; override;
public
destructor Destroy; override;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TmyThread }
constructor TmyThread.Create(AParam1, AParam2: Integer; AUpdateProc: TProc<TMyThread, Integer>;
ACompleteProc: TProc<TMyThread>);
begin
FParam1 := AParam1;
FParam2 := AParam2;
FUpdateProc := AUpdateProc;
FCompleteProc := ACompleteProc;
inherited Create(False);
end;
procedure TmyThread.Execute;
var
I: Integer;
begin
//inherited; - abstract
try
I := FParam1;
while not Terminated and (I < FParam2) do
begin
Sleep(1000);
Inc(I);
QueueUpdateProc(I);
end;
finally
if Assigned(FCompleteProc) then
TThread.Queue(Self, SyncCompleteProc);
end;
end;
procedure TmyThread.QueueUpdateProc(APosition: Integer);
begin
if Terminated or not Assigned(FUpdateProc) then
Exit;
TThread.Queue(Self,
procedure
begin
FUpdateProc(Self, APosition);
end);
end;
procedure TmyThread.SyncCompleteProc;
begin
FCompleteProc(Self);
end;
{ TForm1 }
procedure TForm1.Button1Click(Sender: TObject);
const
param1 = 1;
param2 = 5;
begin
myProgressBar.Min := param1;
myProgressBar.Max := param2 + 1;
myProgressBar.Position := param1;
FThread := TmyThread.Create(param1, param2, ThreadUpdateHandler, ThreadCompletedHandler);
end;
destructor TForm1.Destroy;
begin
if Assigned(FThread) then
FThread.Terminate;
inherited;
end;
procedure TForm1.ThreadCompletedHandler(AThread: TmyThread);
begin
try
if not AThread.Terminated then // check form is not destroye yet
begin
FThread := nil;
myProgressBar.Position := myProgressBar.Max; //an internal component, not really important}
end;
finally
FreeAndNil(AThread);
end;
end;
procedure TForm1.ThreadUpdateHandler(AThread: TMyThread; APosition: Integer);
begin
if not AThread.Terminated then // check form is not destroye yet
myProgressBar.Position := APosition;
end;
procedure TForm1.UpdateActions;
begin
inherited;
Button1.Enabled := not Assigned(FThread);
end;
end.
and DFM file
object Form1: TForm1
Left = 0
Top = 0
Caption = 'Form1'
ClientHeight = 290
ClientWidth = 554
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 24
Top = 16
Width = 75
Height = 25
Caption = 'Button1'
TabOrder = 0
OnClick = Button1Click
end
object myProgressBar: TProgressBar
Left = 120
Top = 108
Width = 150
Height = 17
TabOrder = 1
end
end
NB In this sample form does not wait for the thread so potentially we may close the application before the thread is terminated. It should not be a problem for this sample as it simple enough but in real live you may need to wait for the thread in Form.Destroy or create some thread manager which should wait for all running threads before application is finished.
In the following test program each test thread adds its handle to a global TThreadList when it starts executing, and removes its handle from the same list when its execution is about to end.
For testing purposes, additionally, each thread makes sure it adds its handle before the main thread locks the list (to duplicate their handles and start waiting on them to finish). The threads also make sure they don't remove their handles before the main thread locks the list.
The test program runs fine for about up to 50-60 threads. After that, the WaitForMultipleObjects call start to fail with WAIT_FAILED, GetLastError returns 87 (ERROR_INVALID_PARAMETER). Currently it starts 100 threads. My question is, what am I doing wrong?
The program is compiled with XE2 - update 4, 32-bit target platform. Test box is W7x64.
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
windows,
sysutils,
classes,
syncobjs;
type
TTestThread = class(TThread)
private
FAckStarted: TEvent;
function GetAckHandle: THandle;
class var
ThreadList: TThreadList;
WaitEnd: THandle;
protected
procedure Execute; override;
public
constructor Create;
destructor Destroy; override;
property AckHandle: THandle read GetAckHandle;
end;
{.$DEFINE FREEONTERMINATE}
constructor TTestThread.Create;
begin
inherited Create(True);
FAckStarted := TEvent.Create;
{$IFDEF FREEONTERMINATE}
FreeOnTerminate := True;
{$ENDIF}
end;
destructor TTestThread.Destroy;
begin
FAckStarted.Free;
inherited;
end;
procedure TTestThread.Execute;
begin
// OutputDebugString(PChar(Format('%d starting -------------', [Handle])));
ThreadList.Add(Pointer(Handle));
FAckStarted.SetEvent;
NameThreadForDebugging(AnsiString(IntToStr(Handle)));
WaitForSingleObject(WaitEnd, INFINITE);
ThreadList.Remove(Pointer(Handle));
// OutputDebugString(PChar(Format('%d leaving -------------', [Handle])));
end;
function TTestThread.GetAckHandle: THandle;
begin
Result := FAckStarted.Handle;
end;
const
NumThreads = 100;
var
DeferThreadEnd: TEvent;
ThreadList: array of TThread;
i: Integer;
Thread: TTestThread;
WaitThreadStart: THandle;
LockList: TList;
LockListCount: Integer;
ThreadHandleArr: array of THandle;
WaitRet: DWORD;
begin
IsMultiThread := True;
ReportMemoryLeaksOnShutdown := True;
TTestThread.ThreadList := TThreadList.Create;
DeferThreadEnd := TEvent.Create;
TTestThread.WaitEnd := DeferThreadEnd.Handle;
SetLength(ThreadList, NumThreads);
for i := 0 to NumThreads - 1 do begin
Thread := TTestThread.Create;
ThreadList[i] := Thread;
WaitThreadStart := Thread.GetAckHandle;
Thread.Start;
WaitForSingleObject(WaitThreadStart, INFINITE);
end;
LockList := TTestThread.ThreadList.LockList;
LockListCount := LockList.Count;
SetLength(ThreadHandleArr, LockListCount);
for i := 0 to LockListCount - 1 do
{$IFDEF FREEONTERMINATE}
Win32Check(DuplicateHandle(GetCurrentProcess, THandle(LockList[i]),
GetCurrentProcess, #ThreadHandleArr[i], SYNCHRONIZE, True, 0));
{$ELSE}
ThreadHandleArr[i] := THandle(LockList[i]);
{$ENDIF}
TTestThread.ThreadList.UnlockList;
DeferThreadEnd.SetEvent;
if LockListCount > 0 then begin
Writeln('waiting for ', LockListCount, ' threads');
WaitRet := WaitForMultipleObjects(LockListCount,
PWOHandleArray(ThreadHandleArr), True, INFINITE);
case WaitRet of
WAIT_OBJECT_0: Writeln('wait success');
WAIT_FAILED: Writeln('wait fail:', SysErrorMessage(GetLastError));
end;
end;
for i := 0 to Length(ThreadList) - 1 do begin
{$IFDEF FREEONTERMINATE}
Win32Check(CloseHandle(ThreadHandleArr[i]));
{$ELSE}
ThreadList[i].Free;
{$ENDIF}
end;
DeferThreadEnd.Free;
TTestThread.ThreadList.Free;
Writeln('program end');
Readln;
end.
The WaitForMultipleObjects() documentation states:
The maximum number of object handles is MAXIMUM_WAIT_OBJECTS.
The value of MAXIMUM_WAIT_OBJECTS is 64 (defined in winnt.h), so 100 handles is over the limit. However, the documentation also explains how to overcome that limit:
To wait on more than MAXIMUM_WAIT_OBJECTS handles, use one of the following methods:
Create a thread to wait on MAXIMUM_WAIT_OBJECTS handles, then wait on that thread plus the other handles. Use this technique to break the handles into groups of MAXIMUM_WAIT_OBJECTS.
Call RegisterWaitForSingleObject to wait on each handle. A wait thread from the thread pool waits on MAXIMUM_WAIT_OBJECTS registered objects and assigns a worker thread after the object is signaled or the time-out interval expires.
See the answer to this question for an example of the first technique.
I am trying to write a Delphi code to update the TImage components on a from. I was using C++ for long time but don't have much experience in Delphi;however, I need to use Delphi for some reason. I obtained and modified a similar code to do the task as shown as follows. I have 4 TImages components placed on a form, I start a thread to update the images when the player press the button. I have 6 resource bitmaps with numbered in 1 to 6 embedded in the project and the code will randomly pick and load the resource bitmap into the TImage. I find that using the thread sometimes, some images will not be drawn (just blank like no image being loaded). I know you may be wondering why I need to use a thread and why not just call ShowRanodmImage when the button is pressed. In this simple case, surely I don't have to use thread instead but I am going to apply to some situation that threading must be used. So my question is why the following code will occationally not show the proper image? How to fix that? Thanks a lot.
unit TTUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
type
TTestThread = class;
TTestForm = class(TForm)
NextBTN: TButton;
P11: TPanel;
P12: TPanel;
P13: TPanel;
P14: TPanel;
CELL11: TImage;
CELL12: TImage;
CELL22: TImage;
CELL21: TImage;
procedure FormCreate(Sender: TObject);
procedure NextBTNClick(Sender: TObject);
private
myThread :TTestThread;
pImgs: array[1..2, 1..2] of TImage;
public
procedure ShowRandomImg;
end;
TTestThread = class(TThread)
private
myForm :TTestForm;
protected
procedure Execute(); override;
public
constructor Create(aFrom :TTestForm; CreateSuspended: Boolean);
end;
var
TestForm: TTestForm;
implementation
{$R *.dfm}
procedure TTestForm.FormCreate(Sender: TObject);
begin
myThread := TTestThread.Create(Self, True);
pImgs[1][1]:=CELL11; pImgs[1][2]:=CELL12;
pImgs[2][1]:=CELL21; pImgs[2][2]:=CELL22;
end;
procedure TTestForm.NextBTNClick(Sender: TObject);
begin
myThread.Resume;
end;
procedure TTestForm.ShowRandomImg;
var
i, j :Integer;
r :array[1..2, 1..2] of Integer;
begin
Self.NextBTN.Enabled := false;
r[1][1] := Random(6)+1;
r[1][2] := Random(6)+1;
r[2][1] := Random(6)+1;
r[2][2] := Random(6)+1;
for i := 1 to 2 do
begin
for j := 1 to 2 do
begin
pImgs[i][j].Picture.Bitmap.LoadFromResourceID(HInstance, r[i][j]);
pImgs[i][j].Canvas.Font.Size := 38;
pImgs[i][j].Canvas.Font.Color := clRed;
pImgs[i][j].Canvas.Font.Style := [fsBold];
pImgs[i][j].Canvas.TextOut(20, 20, inttostr(r[i][j]));
end;
end;
Self.NextBTN.Enabled := true;
end;
////////////////////
constructor TTestThread.Create(aFrom: TTestForm; CreateSuspended: Boolean);
begin
inherited Create(CreateSuspended);
Self.myForm := aFrom;
end;
procedure TTestThread.Execute;
begin
inherited;
Repeat
Self.myForm.ShowRandomImg;
Self.Suspend;
Until Terminated;
end;
end.
I'm developing a multithread download application. I have one thread that creates many threads that download data. While downloading I need to see the progress in progress bar, so I set the maximum as the size of the file, and I calculate current downloaded data by using IdHTTPWork, which I added as a procedure of thread (secondary thread). When my app is started, the main thread creates other threads to download (in the loop for) and set the position of begin and end (idhttp.request.range), then each thread starts downloading like this:
HTTP.Request.Range := Format('%d-%d',[begin ,end]);
HTTP.Get(url,fs);
this is the procedure of secondarythread.work:
procedure TSecondaryThread.IdHTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
begin
if AWorkMode = wmRead then
position:= AWorkCount;// position is a global variable
SendMessage(HWND_BROADCAST,MyMessage, 2,position);
end;
I don't know if this is the right code, but I can't find another solution. Each thread can increment position using the value of downloaded data, so position will contain the global downloads in instant S, I don't know if this is true.
Now my questions:
1- the progress doesn't correspond to the current amount of downloaded data; instead, it increments very slowly.
2-when I add -just when I add- Asend message in this procedure, it never stops working!!
So what is the problem?
You have the right idea by giving each worker thread its own TIdHTTP object and its own OnWork event handler. But you are not delivering those status updates to the main thread correctly.
Use PostMessage() instead of SendMessage() so that you do not slow down your worker threads.
You have multiple worker threads posting status updates to the main thread, so DO NOT use a global variable to hold the progress, and certainly DO NOT have the worker threads update that variable directly. Each worker thread should put its current status directly in the parameters of the message that gets posted to the main thread, and then the main thread can have a private counter variable that it increments with each status update.
DO NOT post the status updates using HWND_BROADCAST - that broadcasts the message to every top-level window in the system! Post the messages only to your main thread, by posting to an HWND that belongs to the main thread (I would suggest using AllocateHWnd() for that).
Try something like this:
unit StatusUpdates;
uses
Windows;
interface
type
PStatus = ^TStatus;
TStatus = record
BytesDownloadedThisTime: Int64;
BytesDownloadedSoFar: Int64;
MaxBytesBeingDownloaded: Int64;
end;
var
StatusUpdateWnd: HWND = 0;
implementation
end.
uses
..., StatusUpdates;
type
TMainForm = class(TForm)
...
private
TotalDownloaded: Int64;
...
end;
procedure TMainForm.FormCreate(Sender: TObject);
begin
StatusUpdateWnd := AllocateHWnd(StatusWndProc);
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
if StatusUpdateWnd <> 0 then
begin
DeallocateHWnd(StatusUpdateWnd);
StatusUpdateWnd := 0;
end;
end;
procedure TMainForm.StartDownload;
begin
ProgressBar1.Position := 0;
ProgressBar1.Max := FileSizeToBeDownloaded;
TotalDownloaded := 0;
// create download threads...
end;
procedure TMainForm.StatusWndProc(var Message: TMessage);
var
Status: PStatus;
begin
if Message.Msg = MyMessage then
begin
Status := PStatus(Message.LParam);
try
if Status.BytesDownloadedThisTime > 0 then
begin
Inc(TotalDownloaded, Status.BytesDownloadedThisTime);
ProgressBar1.Position := TotalDownloaded;
end;
// use Status for other things as needed...
finally
Dispose(Status);
end;
end else
Message.Result := DefWindowProc(StatusUpdateWnd, Message.Msg, Message.WParam, Message.LParam);
end;
uses
..., StatusUpdates;
type
TSecondaryThread = class(TThread)
private
FTotalBytes: Int64;
FMaxBytes: Int64;
procedure PostStatus(BytesThisTime: Int64);
...
end;
procedure TSecondaryThread.PostStatus(BytesThisTime: Int64);
var
Status: PStatus;
begin
New(Status);
Status.BytesDownloadedThisTime := BytesThisTime;
Status.BytesDownloadedSoFar := FTotalBytes;
Status.MaxBytesBeingDownloaded := FMaxBytes;
if not PostMessage(StatusUpdateWnd, MyMessage, 2, LPARAM(Status)) then
Dispose(Status);
end;
procedure TSecondaryThread.IdHTTPWorkBegin(ASender: TObject; AWorkMode: TWorkMode; AWorkCountMax: Int64);
begin
if AWorkMode = wmRead then
begin
FTotalBytes := 0;
FMaxBytes := AWorkCountMax;
PostStatus(0);
end;
end;
procedure TSecondaryThread.IdHTTPWork(ASender: TObject; AWorkMode: TWorkMode; AWorkCount: Int64);
var
BytesThisTime: Int64;
begin
if AWorkMode = wmRead then
begin
BytesThisTime := AWorkCount - FTotalBytes;
FTotalBytes := AWorkCount;
PostStatus(BytesThisTime);
end;
end;
I need a function that will use my cpu 100% . I have a single core cpu any ideas ?
Does the thread need to be set in high priority ?
A loop (infinite) usually does the job fine:
while true do begin
end;
Obviously forcing the CPU go to 100% without doing anything useful should be done only for academic purposes and on your own hardware and never in any real application that you intend to ship.
For example, by the next thread:
unit Unit2;
interface
uses
Classes;
type
TLoadThread = class(TThread)
public
class var Stop: Boolean;
protected
procedure Execute; override;
public
constructor Create;
end;
implementation
constructor TLoadThread.Create;
begin
inherited Create;
FreeOnTerminate:= True;
end;
procedure TLoadThread.Execute;
begin
repeat until Stop;
end;
end.
A usage example:
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
end;
var
Form1: TForm1;
implementation
uses Unit2;
{$R *.dfm}
procedure LoadCPU;
begin
TLoadThread.Stop:= False;
TLoadThread.Create;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
LoadCPU;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
TLoadThread.Stop:= True;
end;
if your CPU has N cores, click Button1 N times to reach 100% CPU load; no need to increase thread priority.