Root > Advanced topics > Multi-threaded applications > Creating threads > Thread Pools

Thread Pools

Previous pageReturn to chapter overviewNext page   

Important: as a general rule, you should write your application in a such way that it will behave correctly without EurekaLog on board. This means that if your or 3rd party code throws an exception in a thread - it must be handled in a way that you expect. For example, by showing an error message, canceling action, retrying, etc. Once your application behaves like it is supposed to do - then you add EurekaLog for it. EurekaLog will auto-handle all unhandled exceptions automatically (by hooking few well-known places), and for everything else - you can call EurekaLog from your own handlers when needed. In other words:

Incorrect: your application does not show messages for exceptions in thread and you are adding EurekaLog in hopes to fix this behavior;
Correct: your application correctly handles thread exceptions and you are adding EurekaLog to receive reports about such exceptions.

 

A thread pool is a collection of worker threads that efficiently execute asynchronous callbacks on behalf of the application. The thread pool is primarily used to reduce the number of application threads and provide management of the worker threads.

 

Delphi and C++ Builder do not have any implementations for thread pools out of the box. Applications that want to use thread pool may utilize system API (which is most commonly used in the form of QueueWorkItem function) or frameworks. The specifics are different for each API/framework, but there is a common scheme: you call some "create" function and pass your function as argument (a callback) - which is called a "task". This action will schedule your function (i.e. callback/task) to be executed by some thread from thread pool at the specified moment. An important moment here is that you don't know which thread will run your code. Thread for each task is selected (assigned) by thread pool manager. Primary task of thread pool is re-using threads to avoid performance penalty for creating and terminating threads. This means that single thread from thread pool will execute many different tasks. If you schedule same task multiple times - there is no guarantee that your task will be executed by the same thread from thread pool.

 

This means that arbitrary code is not safe for thread pooling. Task function (and all functions called from task function) must be thread-pool safe. A safe function does not assume that the thread executing it is a dedicated or persistent thread. In general, you should avoid using thread local storage or making an asynchronous call that requires a persistent thread, such as the RegNotifyChangeKeyValue function. A normal rule of thumb for thread pool tasks: do not change thread state. For example, you should not terminate thread, you should not alter FPU state, you should not alter thread priority, you should not let exceptions escape your task function, etc. So, if you need to perform non-thread-pool safe action - you need to revert it before returning control from your task function.

 

An example of a good thread-pool function:

 

function MyTask(lpThreadParameter: Pointer): Integer; stdcall;
begin
  try
    // ... your task code ...
  except
    // Handle any exception in task
    ApplicationHandleException(nil); // from Classes unit
  end;
  Result := 0; // Result is ignored
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin

  IsMultiThreaded := True;

 

  // Execute MyTask in a background thread
  Win32Check(QueueUserWorkItem(MyTask, nil, 0));
end;

 

See also: how to handle an exception.

 

Since a recommended approach is to manually control activation of EurekaLog per each thread - we suggest you to use such code:

 

function MyTask(lpThreadParameter: Pointer): Integer; stdcall;
begin
  // Mark thread for yourself
  NameThread('This is thread pool thread with my task');
  SetEurekaLogStateInThread(0, True); 
  try
    try
      // ... your task code ...
      Result := 0; // <- to indicate success
    except
      on E: Exception do
        Result := HandleException(E); // <- to handle exception and indicate failure
    end;
  finally
    // Clean after yourself
    // (i.e. mark/prepare thread for other tasks)
    NameThread('');
    SetEurekaLogStateInThread(0, False); 
  end;
end;
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  IsMultiThreaded := True;

 

  // Execute MyTask in a background thread
  Win32Check(QueueUserWorkItem(MyTask, nil, 0));
end;

 

See also: how to handle an exception.

 

This sample code enables EurekaLog (and sets thread name) only for the duration of our task. When our task is completed, thread will be released into thread pool - so we disable EurekaLog (because we don't know which task will use this thread in the next time).

 

Important note: EurekaLog has to be enabled for background threads. And the same "thread persistent state" rule applies to EurekaLog per-thread state: you should clean after your task - as indicated in the above example.

 

Some thread pool implementations may have less restrictive rules. For example, thread pool implementations in Delphi/C++ Builder frameworks will probably have exception handling code established. See working with multithreading frameworks for more information.

 

 

See also:




Send feedback... Build date: 2023-09-11
Last edited: 2023-03-07
PRIVACY STATEMENT
The documentation team uses the feedback submitted to improve the EurekaLog documentation. We do not use your e-mail address for any other purpose. We will remove your e-mail address from our system after the issue you are reporting has been resolved. While we are working to resolve this issue, we may send you an e-mail message to request more information about your feedback. After the issues have been addressed, we may send you an email message to let you know that your feedback has been addressed.


Permanent link to this article: https://www.eurekalog.com/help/eurekalog/multithreading_pools.php