Link to home
Start Free TrialLog in
Avatar of fuluppi
fuluppi

asked on

demo project

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


Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

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
Avatar of fuluppi
fuluppi

ASKER

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....
Avatar of fuluppi

ASKER

so, my gloabl var is read only. do you still think this is a problem ?
if the global variable is read-only then it should be a constant.
Avatar of fuluppi

ASKER

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
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 ?



ASKER CERTIFIED SOLUTION
Avatar of Wim ten Brink
Wim ten Brink
Flag of Netherlands image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of fuluppi

ASKER

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 ?
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...
Avatar of fuluppi

ASKER

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 ;-)
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.