views:

438

answers:

3

I found this on the Dr Dobbs site today at http://www.ddj.com/hpc-high-performance-computing/220300055?pgno=3 It's a nice suggestion regarding thread implmentation. What is best way of achieving this with TThread in Delphi I wonder? Thanks Brian

=== From Dr Dobbs ==============

Make multithreading configurable! The number of threads used in a program should always be configurable from 0 (no additional threads at all) to an arbitrary number. This not only allows a customization for optimal performance, but it also proves to be a good debugging tool and sometimes a lifesaver when unknown race conditions occur on client systems. I remember more than one situation where customers were able to overcome fatal bugs by switching off multithreading. This of course does not only apply to multithreaded file I/O.

Consider the following pseudocode:

int CMyThreadManger::AddThread(CThreadObj theTask)
{
    if(mUsedThreadCount >= gConfiguration.MaxThreadCount())
        return theTask.Execute(); // execute task in main thread
    // add task to thread pool and start the thread
    ...
}

Such a mechanism is not very complicated (though a little bit more work will probably be needed than shown here), but it sometimes is very effective. It also may be used with prebuilt threading libraries such as OpenMP or Intel's Threaded Building Blocks. Considering the measurements shown here, its a good idea to include more than one configurable thread count (for example, one for file I/O and one for core CPU tasks). The default might probably be 0 for file I/O and <number of cores found> for CPU tasks. But all multithreading should be detachable. A more sophisticated approach might even include some code to test multithreaded performance and set the number of threads used automatically, may be even individually for different tasks.

===================

A: 

I generally have only one class inheriting from TThread, one that takes 'worker items' from a queue or stack, and have them suspend when no more items are available. The main program can then decide how many instances of this thread to instantiate and start. (using this config value).

This 'worker items queue' should also be smart enough to resume suspended threads or create a new thread when required (and when the limit permits it), when a worker item is queued or a thread has finished processing a worker item.

Stijn Sanders
There is no need at all to suspend and resume threads. Let each thread wait on an event or semaphore, or use `WaitMessage()` with a thread message loop (may be necessary for OLE). There's a lot of discussion about the topic here on SO under [delphi], read it, but it's best to ignore the arguments of people who think they know better than Embarcadero and Microsoft devs, and try to tell you using `Suspend()` and `Resume()` would be fine.
mghie
+5  A: 

I would create an abstract class TTask. This class is meant to executes the task. With the method Execute:

type

  TTask = abstract class
  protected
    procedure DoExecute; virtual; abstract;
  public
    procedure Execute;
  end;

  TTaskThread = class (TThread)
  private
    FTask : TTask;
  public
    constructor Create(const ATask: TTask); 
    // Assigns FTask and enables thread, free on terminate.

    procedure Execute; override; // Calls FTask.Execute.
 end;

The method Execute checks the number of threads. If the max is not reached, it starts a thread using TTaskThread that calls DoExecute and as such execute the task in a thread. If the max is reached, DoExecute is called directly.

Gamecat
+4  A: 

The answer by Gamecat is good as far as the abstract task class is concerned, but I think calling DoExecute() for a task in the calling thread (as the article itself does too) is a bad idea. I would always queue the tasks to be executed by background threads, unless threading was disabled completely, and here's why.

Consider the following (contrived) case, where you need to execute three independent CPU-bound procedures:

Procedure1_WhichTakes200ms;
Procedure2_WhichTakes400ms;
Procedure3_WhichTakes200ms;

For better utilisation of your dual core system you want to execute them in two threads. You would limit the number of background threads to one, so with the main thread you have as many threads as cores.

Now the first procedure will be executed in a worker thread, and it will finish after 200 milliseconds. The second procedure will start immediately and be executed in the main thread, as the single configured worker thread is already occupied, and it will finish after 400 milliseconds. Then the last procedure will be executed in the worker thread, which has already been sleeping for 200 milliseconds now, and will finish after 200 milliseconds. Total execution time 600 milliseconds, and for 2/3 of that time only one of both threads was actually doing meaningful work.

You could reorder the procedures (tasks), but in real life it's probably impossible to know in advance how long each task will take.

Now consider the common way of employing a thread pool. As per configuration you would limit the number of threads in the pool to 2 (number of cores), use the main thread only to schedule the threads into the pool, and then wait for all tasks to complete. With above sequence of queued tasks thread 1 would take the first task, thread two would take the second task. After 200 milliseconds the first task would complete, and the first worker thread would take the third task from the pool, which is empty afterwards. After 400 milliseconds both the second and the third task would complete, and the main thread would be unblocked. Total time for execution 400 milliseconds, with 100% load on both cores in that time.

At least for CPU-bound threads it's of vital importance to always have work queued for the OS scheduler. Calling DoExecute() in the main thread interferes with that, and shouldn't be done.

mghie