Solved

demo project

Posted on 2004-08-23
11
404 Views
Last Modified: 2010-04-05
hi !
i want to write a small program, i have an idea of the architecture but doesn't know how
to program it. i guess it would take 10 minutes for an expert to make me a small
example project ;-) this would really help me. the project should do the following:

the main project includes a global variable test : Integer;
and a button Button1, when you click it, test is increased

There also exist an ini file which conatains a number ie. 10

Now comes the interesting and for me problematic point:

i want to create 10 (or more, depending on the ini file) objects which do the following:

when button1 is pressed, each single object shall access the value
of the global var test.

this shall happen for all objects at the same time. i think it's multi threaded ?
the objects shall not be informed about the change of test one after
one.

the objects are existing for the whole runtime.

is that complicated to do ? i have no idea how to do that. i only programmed
"advanced procedural" yet ;-)

thanks
philip


0
Comment
Question by:fuluppi
11 Comments
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
The use of global variables is discouraged in general, especially if you want to create multi-threaded applications. It's definitely not wise to use global variables in a multithreaded applications because of synchronisation problems. And 10 objects accessing the data all at the same time will require 10 CPU's in your system... :-)

Furthermore, this seems more like a homework assignment to me. A weird assignment, but still... It seems you're making it too complicated for yourself...

For the objects, use a dynamic array: var MyObjects: array of TMyObjects;
Set the length of this array: SetLength(MyObjects, ValueReadFromINIfile);
Create the objects: for I := Low(MyObjects) to High(MyObjects) do MyObjects := TMyObjects.Create;
Doing some action for them all: for I := Low(MyObjects) to High(MyObjects) do MyObjects.DoSomethings(Test);
Deleting them all: for I := Low(MyObjects) to High(MyObjects) do MyObjects.Free;

It's up to you to see how to use this further. :P
0
 

Author Comment

by:fuluppi
Comment Utility
haha no homework. ok, i'll explain my task:

i receive a TBitmap from a video source.
I want the "objects" to access special parts of the image each time i receive a new frame.
to make it easier i didn't mention that "test" is a Bitmap....
0
 

Author Comment

by:fuluppi
Comment Utility
so, my gloabl var is read only. do you still think this is a problem ?
0
 
LVL 2

Expert Comment

by:beermequik
Comment Utility
if the global variable is read-only then it should be a constant.
0
 

Author Comment

by:fuluppi
Comment Utility
it is read only for the object. the main application changes it, when a new
image arrives and informs the objects about it. can anybody of you
write me a demo project ? i have no idea how to create the objects ....

thanks
0
Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

 
LVL 8

Expert Comment

by:BdLm
Comment Utility
If program a opens a File, program B may only open this file read only, working with flat files will create many synchronisation problems.

What about a client-server architceture for this problem. The bitmap server will receive a new image from the video source, he will trigger the clients that some new stuff is available; The server will send the Bitmap data to the clients, the clients have to process the image data and send it back to the server, the server has to update the master-Bitmap.....; the processing synchronisation is done by server, the clients take care for image processing.

Is it a overkill for your task ?



0
 
LVL 17

Accepted Solution

by:
Wim ten Brink earned 500 total points
Comment Utility
:-) Okay, not homework then. It was a strange assignment anyway.

Well, a global read-only variable. Well, as long as you don't change anything in it, there should be no problem using multiple threads to access it. For reading purposes only, of course. But as soon as you start changing one bit in that variable or the image behind it, you might cause some hard-to-discover bug to haunt you. Thus, no drawing over this image unless you're locking and unlocking the image for every change.
And as I just have shown you, creating and destroying should be no problem since Delphi 5 supports dynamic arrays. But using multi-threading to access the bitmap all at the same time? Don't see a real need for this here. If your system has only a single processor, it would not be useful anyways. All you get by using multiple threads is that all objects will do something with your image at the same time, while normally they would just wait for each other. In time consumption it would be just as slow. If you have a multi-processor system then you'd be able to gain somthing from such an approach, though.

You could avoid the use of a global variable by passing the variable as a parameter to the objects, though. This is considered to be a nicer technique although it doesn't have much effect in the final product.

Now, if you really want to create a multithreading application then keep in mind that those threads will probably be waiting for any events too. Basically, a thread should just like the main application have some loop that keeps looping until a certain condition is reached. Often something like:

procedure TMyThread.Execute;
begin
  while not Terminated do begin
    DoSomething;
  end;
end;

Now, the DoSomething in this code is probably a piece of code that keeps the thread asleep until some event triggers it. And not the Delphi event but a Windows event. In general, the WaitForSingleObject() Windows API waiting wor an Event, Mutex, Semaphore or some other Windows object. Once it's triggered it starts processing and once done, goes back to the start of the loop waiting for another event.
Often, WaitForSingleObject() will just wait a single second and on a timeout it won't execute anything but check if it's time to terminate. Otherwise, it might wait forever for an event that never happens.
Now, when your user clicks on the button, all you do is trigger the event that all threads are waiting for. Then they do whatever you want them to do and start waiting again.
But okay, a console application as example of how to use a random number of threads. Here it comes.

program Threading;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils, Classes;

var
  Global_Ichy_Variable: Integer;

type // The thread class.
  TMyThread = class( TThread )
  private
    FEvent: THandle; // An event handle. Used to wait for events.
    FID: Integer; // An ID for the thread. Just for fun to show that threads run in random order.
  protected
    procedure Execute; override; // The thread loop.
  public
    constructor Create( const ID: Integer ); // To create a new instance with a certain ID number.
    destructor Destroy; override; // Needed to free the event.
    procedure Trigger; // Will trigger the event.
  end;

constructor TMyThread.Create;
begin
  // We don't want it to start running immediately.
  inherited Create( True );
  // The event tells when we can do something.
  FEvent := CreateEvent( nil, False, False, nil );
  // Assign the ID.
  FID := ID;
  // The thread can terminate itself.
  FreeOnTerminate := True;
  // Now, the thread can run!
  Resume;
end;

destructor TMyThread.Destroy;
begin
  try
    // Kill the event handle.
    CloseHandle( FEvent );
    // Just tell that the thread is closed.
    WriteLn( 'Thread ', FID, ' finished.' );
  finally inherited;
  end;
end;

procedure TMyThread.Execute;
var
  Result: DWORD;
  Something: Integer;
begin
  // Check if we're still running.
  while not Terminated do begin
    // Wait for timeout or event.
    Result := WaitForSingleObject( FEvent, 500 );
    if ( Result = WAIT_OBJECT_0 ) then begin
      // Event triggered. Do something with the global thingie...
      Something := Global_Ichy_Variable;
      // Oh, and generate some output.
      WriteLn( 'Thread ', FID, ' -> ', Something ); // Will probably write some chaos since it's not synchronized!
    end
    else if ( Result = WAIT_TIMEOUT ) then begin
      // Timeout, just ignore.
    end
    else begin
      // Error, quit.
      Terminate;
    end;
  end;
end;

procedure TMyThread.Trigger;
begin
  // Set the event, IE start the next processing loop.
  SetEvent( FEvent );
end;

var
  MyThreads: array of TMyThread; // List of threads.
  I: Integer; // To walk around the list.
begin
  // We're working with random numbers so keep it a bit random...
  Randomize;
  // Set a value for our global variable.
  Global_Ichy_Variable := 10;
  // Create a random number of elements.
  SetLength( MyThreads, Random( 5 ) + 5 );
  // How many do we have?
  WriteLn( Length( MyThreads ), ' items.' );
  // We've claimed space for the thread objects but just haven't created them. Let's start creating.
  for I := Low( MyThreads ) to High( MyThreads ) do
    MyThreads[ I ] := TMyThread.Create( I );
  // Some repeating action... This could symbolize a button press.
  while ( Global_Ichy_Variable > 0 ) do begin
    // Wait for the "button" to be pressed.
    WriteLn( 'Press <Enter>' );
    ReadLn;
    // Button pressed! Trigger ALL events one by one...
    for I := Low( MyThreads ) to High( MyThreads ) do
      MyThreads[ I ].Trigger;
    // Well, we can modify the global variable here, but this is VERY UNSAFE!!!
    Global_Ichy_Variable := Global_Ichy_Variable - 1;
    // Then again, it's unsafe just because the threads might be using a completely wrong value...
  end;
  // Now tell the threads to terminate...
  for I := Low( MyThreads ) to High( MyThreads ) do
    MyThreads[ I ].Terminate;
  // In the background, the threads are executing the last event and are terminating.
  Write( 'Done! Press <Enter>' );
  // We could be here BEFORE all threads terminated!
  ReadLn;
end.

Now, this code does need a lot of optimizations, tricks to synchrinize better, perform faster and do some more useful logic. Also keep in mind that the threads and main thread are almost running at the same time. The main thread might change the value of Global_Ichy_Variable before all threads have read the correct value. This could cause some nasty bugs. It's not easy to get multi-threaded applications running smoothly since you have to keep in mind that everything wants to access everything at the same time. It takes a while before you're used to it anyway...
0
 

Author Comment

by:fuluppi
Comment Utility
this helps me. how could i pass the TBItmap as variable ? i get 6 bitmaps per second and i
need to send them to at least 10 objects. wouldn't this cost much more memory than
the global var ?
0
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
Normally, you just use pointers to the bitmaps. However, since you also need to know when the threads are done processing those bitmaps so you can free them, you do have a bit more problems.
But it's okay to change the trigger function in:

procedure TMyThread.Trigger(ABitmap:TBitmap);
begin
  // Set the local bitmap.
  FBitmap.Assign(ABitmap); // Added to the local variables, of course.
  // Set the event, IE start the next processing loop.
  SetEvent( FEvent );
end;

Means your Create method must create this local bitmal copy:
  FBitmap := TBitmap.Create;

And your destructor must free it again:
  FBitmap.Free;

Now, once you trigger a thread, you also have a local copy of the bitmap. The only problem could be that the next trigger happens before the thread is done processing the bitmap. Thus the processing part in the Execute method and the Trigger method must be protected by a critical section. And this is a local critical section. So lets redo the whole code...

program Threading;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils, Classes, Graphics;

var
  Global_Ichy_Variable: TBitmap;

type // The thread class.
  TMyThread = class( TThread )
  private
    FEvent: THandle; // An event handle. Used to wait for events.
    FID: Integer; // An ID for the thread. Just for fun to show that threads run in random order.
    FBitmap: TBitmap; // A bitmap.
    FCriticalSection: TRTLCriticalSection; // Avoiding two pieces of code working on the same thing.
  protected
    procedure Execute; override; // The thread loop.
  public
    constructor Create( const ID: Integer ); // To create a new instance with a certain ID number.
    destructor Destroy; override; // Needed to free the event.
    procedure Trigger( Bitmap: TBitmap ); // Will trigger the event.
  end;

constructor TMyThread.Create;
begin
  // We don't want it to start running immediately.
  inherited Create( True );
  // The event tells when we can do something.
  FEvent := CreateEvent( nil, False, False, nil );
  // Assign the ID.
  FID := ID;
  // Create a local bitmap.
  FBitmap := TBitmap.Create;
  // The thread can terminate itself.
  FreeOnTerminate := True;
  // Create the critical section.
  InitializeCriticalSection( FCriticalSection );
  // Now, the thread can run!
  Resume;
end;

destructor TMyThread.Destroy;
begin
  try
    // Kill the event handle.
    CloseHandle( FEvent );
    // Close the bitmap.
    FBitmap.Free;
    // Close the critical section.
    DeleteCriticalSection( FCriticalSection );
    // Just tell that the thread is closed.
    WriteLn( 'Thread ', FID, ' finished.' );
  finally inherited;
  end;
end;

procedure TMyThread.Execute;
var
  Result: DWORD;
begin
  // Check if we're still running.
  while not Terminated do begin
    // Wait for timeout or event.
    Result := WaitForSingleObject( FEvent, 500 );
    if ( Result = WAIT_OBJECT_0 ) then begin
      // This piece of code requires a protected resource!
      EnterCriticalSection( FCriticalSection );
      // Event triggered. Do something with the global thingie...
      WriteLn( 'Thread ', FID, ' -> Width = ', FBitmap.Width, ',Height = ', FBitmap.Height ); // Will probably write some chaos since it's not synchronized!
      LeaveCriticalSection( FCriticalSection );
    end
    else if ( Result = WAIT_TIMEOUT ) then begin
      // Timeout, just ignore.
    end
    else begin
      // Error, quit.
      Terminate;
    end;
  end;
end;

procedure TMyThread.Trigger;
begin
  // Assign the new bitmap, the protected way...
  EnterCriticalSection( FCriticalSection );
  FBitmap.Assign( Bitmap );
  LeaveCriticalSection( FCriticalSection );
  // Set the event, IE start the next processing loop.
  SetEvent( FEvent );
end;

var
  MyThreads: array of TMyThread; // List of threads.
  I: Integer; // To walk around the list.
  Count: Integer;
begin
  // We're working with random numbers so keep it a bit random...
  Randomize;
  // Set a value for our global variable.
  Global_Ichy_Variable := TBitmap.Create;
  // Create a random number of elements.
  SetLength( MyThreads, Random( 5 ) + 5 );
  // How many do we have?
  WriteLn( Length( MyThreads ), ' items.' );
  // We've claimed space for the thread objects but just haven't created them. Let's start creating.
  for I := Low( MyThreads ) to High( MyThreads ) do
    MyThreads[ I ] := TMyThread.Create( I );
  // Some repeating action... This could symbolize a button press.
  Count := 10;
  while ( Count > 0 ) do begin
    Global_Ichy_Variable.Width := Count + 5;
    Global_Ichy_Variable.Height := 20 - Count;
    // Wait for the "button" to be pressed.
    WriteLn( 'Step ', Count, ', Press <Enter>' );
    ReadLn;
    // Button pressed! Trigger ALL events one by one...
    for I := Low( MyThreads ) to High( MyThreads ) do
      MyThreads[ I ].Trigger( Global_Ichy_Variable );
    // Well, we can modify the global variable here, but this is VERY UNSAFE!!!
    Dec( Count );
    // Then again, it's unsafe just because the threads might be using a completely wrong value...
  end;
  // Free the global bitmap.
  Global_Ichy_Variable.Free;
  // Now tell the threads to terminate...
  for I := Low( MyThreads ) to High( MyThreads ) do
    MyThreads[ I ].Terminate;
  // In the background, the threads are executing the last event and are terminating.
  WriteLn( 'Done! Press <Enter>' );
  // We could be here BEFORE all threads terminated!
  ReadLn;
end.

This time the FBitmap within the Execute method between the Enter/Leave is safe to use in whatever way you like. You're reusing a pre-created bitmap object here so you don't need to free it until the end.
The drawback is of course that you create 10 copies of the bitmap in memory and if these are big bitmaps, it will slow things down. You could share a single bitmap, of course, but if your main thread is updating this image, you never know when you can draw again over this bitmap. Thus all your threads would have to tell you when they're done processing, probably requiring a semaphore or more events. A mutex would not be usable in this case since you're maintaining multiple threads who all have to respond.
But this is a problem you'll always have to deal with. You might get the images faster than you can process... Maybe I'll create another example with a semaphore...
0
 

Author Comment

by:fuluppi
Comment Utility
actually i want the threads just to extract a special part of the main bitmap (each thread another part) and then
perform a motion detection on it. oh man. this will become fun. i'll play around with it a little ;-)
0
 
LVL 17

Expert Comment

by:Wim ten Brink
Comment Utility
Okay, this time a version with a semaphore instead of a critical section. What makes it even better is that it doesn't create copies of the original bitmap but it forces the main thread to wait until all threads are done processing.

program Threading;

{$APPTYPE CONSOLE}

uses
  Windows, SysUtils, Classes, Graphics;

var
  Global_Ichy_Variable: TBitmap;
  Semaphore: THandle; // A global semaphore. Should be okay in most cases.

type // The thread class.
  TMyThread = class( TThread )
  private
    FEvent: THandle; // An event handle. Used to wait for events.
    FID: Integer; // An ID for the thread. Just for fun to show that threads run in random order.
  protected
    procedure Execute; override; // The thread loop.
  public
    constructor Create( const ID: Integer ); // To create a new instance with a certain ID number.
    destructor Destroy; override; // Needed to free the event.
    procedure Trigger( Bitmap: TBitmap ); // Will trigger the event.
  end;

constructor TMyThread.Create;
begin
  // We don't want it to start running immediately.
  inherited Create( True );
  // The event tells when we can do something.
  FEvent := CreateEvent( nil, False, False, nil );
  // Assign the ID.
  FID := ID;
  // The thread can terminate itself.
  FreeOnTerminate := True;
  // Now, the thread can run!
  Resume;
end;

destructor TMyThread.Destroy;
begin
  try
    // Kill the event handle.
    CloseHandle( FEvent );
    // Release a semaphore.
    ReleaseSemaphore( Semaphore, 1, nil );
    // Just tell that the thread is closed.
    WriteLn( 'Thread ', FID, ' finished.' );
  finally inherited;
  end;
end;

procedure TMyThread.Execute;
var
  Result: DWORD;
begin
  // Check if we're still running.
  while not Terminated do begin
    // Wait for timeout or event.
    Result := WaitForSingleObject( FEvent, 500 );
    if ( Result = WAIT_OBJECT_0 ) then begin
      // Lock one semaphore and wait until we can continue.
      WaitForSingleObject( Semaphore, INFINITE );
      // Event triggered. Do something with the global thingie...
      WriteLn( 'Thread ', FID, ' -> Width = ', Global_Ichy_Variable.Width, ',Height = ', Global_Ichy_Variable.Height ); // Will probably write some chaos since it's not synchronized!
      // Release the semaphore.
      ReleaseSemaphore( Semaphore, 1, nil );
    end
    else if ( Result = WAIT_TIMEOUT ) then begin
      // Timeout, just ignore.
    end
    else begin
      // Error, quit.
      Terminate;
    end;
  end;
end;

procedure TMyThread.Trigger;
begin
  // Set the event, IE start the next processing loop.
  SetEvent( FEvent );
end;

var
  MyThreads: array of TMyThread; // List of threads.
  I: Integer; // To walk around the list.
  Count: Integer;
begin
  // We're working with random numbers so keep it a bit random...
  Randomize;
  // Set a value for our global variable.
  Global_Ichy_Variable := TBitmap.Create;
  // Create a random number of elements.
  SetLength( MyThreads, Random( 5 ) + 5 );
  // How many do we have?
  WriteLn( Length( MyThreads ), ' items.' );
  // We've claimed space for the thread objects but just haven't created them. Let's start creating.
  for I := Low( MyThreads ) to High( MyThreads ) do
    MyThreads[ I ] := TMyThread.Create( I );
  // Create the Semaphore.
  Semaphore := CreateSemaphore( nil, Length( MyThreads ), Length( MyThreads ), nil );
  // Some repeating action... This could symbolize a button press.
  Count := 10;
  while ( Count > 0 ) do begin
    Global_Ichy_Variable.Width := Count + 5;
    Global_Ichy_Variable.Height := 20 - Count;
    // Wait for the "button" to be pressed.
    WriteLn( 'Step ', Count, ', Press <Enter>' );
    ReadLn;
    // Button pressed! Trigger ALL events one by one...
    for I := Low( MyThreads ) to High( MyThreads ) do
      MyThreads[ I ].Trigger( Global_Ichy_Variable );
    // Now we have to wait until all threads have released the semaphore.
    for I := Low( MyThreads ) to High( MyThreads ) do
      WaitForSingleObject( Semaphore, INFINITE );
    // Done waiting. All threads are finished.
    ReleaseSemaphore( Semaphore, Length( MyThreads ), nil );
    // Give the other processes time to process.
    Sleep( 0 );
    // Well, we can modify the global variable here, but this is VERY UNSAFE!!!
    Dec( Count );
    // Then again, it's unsafe just because the threads might be using a completely wrong value...
  end;
  // Now tell the threads to terminate...
  for I := Low( MyThreads ) to High( MyThreads ) do begin
    // We must wait for all threads to stop first before releasing the semaphore.
    WaitForSingleObject( Semaphore, INFINITE );
    MyThreads[ I ].Terminate;
  end;
  // Now we have to wait until all threads have terminated.
  for I := Low( MyThreads ) to High( MyThreads ) do
    WaitForSingleObject( Semaphore, INFINITE );
  CloseHandle( Semaphore );
  // Free the global bitmap.
  Global_Ichy_Variable.Free;
  // In the background, the threads are executing the last event and are terminating.
  WriteLn( 'Done! Press <Enter>' );
  // We could be here BEFORE all threads terminated!
  ReadLn;
end.

As you see, no critical section and not even a local variable inside the thread. The biggest problem with semaphores is detecting when everything is done. You could also use a global counter in a loop for this, but then you'd continue to wait in a loop eating lots of processor space just waiting. The line:
  for I := Low( MyThreads ) to High( MyThreads ) do WaitForSingleObject( Semaphore, INFINITE );
Is actually doing the same as a counter but is way less processor-intensive. A LOT less...

Keep in mind that I wait twice for the semaphores. One time in the trigger for the bitmap processing and one time in the end to wait for all threads to end. This last thing is needed because I need to know when the bitmap can be freed and the semaphore destroyed.

There are probably many other people with many other solutions. But I hope these examples are useful to you.

Keep in mind that the semaphore is set to allow as many calls to it as the number of items you have. However, with a large number of threads, you might actually choose a LOWER number for the semaphore than then number of threads you have. You could, for example, have 500 threads but initialize the semaphore as:
  Semaphore := CreateSemaphore( nil, 10, 10, nil );
This would allow only 10 threads to run at the same time, not all of them. But it also means that you have to change the wait_for_everything line into:
  for I := 1 to 10 do WaitForSingleObject( Semaphore, INFINITE );
Because you don't have reserved space for 500 but for only 10 counters. :-)

I think a semaphore is more what you're looking for.
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

Suggested Solutions

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

772 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

15 Experts available now in Live!

Get 1:1 Help Now