Manager Worker Threads System

From Lazarus wiki
Revision as of 18:10, 21 December 2008 by Aurawin (talk | contribs)
Jump to navigationJump to search

Lazarus offers access to FPC's multi-threaded environment libraries under Linux and Windows. If you are looking to develop lightning fast native engines specific to Linux or even Windows 64 this system is a great start to helping you understand how best to leverage multiple cores that modern scientific applications require to process vast amounts of data in real-time.

This example is meant to get you thinking on how best to engineer a multi-threaded system that is re-entrant in nearly every aspect, as well as to demonstrate how to use Critical Sections to protect memory objects by synchronizing access to such objects.

One Manager Thread

Define as many of the lists of objects, data structures, and Critical Sections here as well as the list of Threads needed to bring your system up to scale. In the example provided, creating an instance of a Manager Thread will require the scale factor desired. Since this is just a simple example, that scale factor is static but with propper locking mechanisms this system could be upgraded to feature dynamic scaling.

Collection of Worker Threads

The Manager thread maintains a list of Thread objects it "manages" and typically does not do much to interfere with operations of each worker thread. The best way to design highly efficient multi-threaded systems is to code worker threads as tight as you can with as little critical sections as possible. And if you need to lock remember that all other threads at that instant in time could be placed in a wait state until that lock is released.

Critical Sections

When protecting data from multiple threads from writing to data at the same place a Critical Section is needed. Critical Sections are supported and if you have any Windows Experience it is similar with a few caveats which are discussed in this article.

InitCriticalSection(Lock : TRTLCriticalSection) - The name of this procedure is different from Windows API where the old name was InitializeCriticalSection. You must call this procedure because it is required for locks to perform.

DoneCriticalSection(Lock : TRTLCriticalSection) - The name of this procedure is also different from Windows API where the old name was DeleteCriticalSection. You must call this procedure because it enables the Operating System to free memory it allocated to perform locking on your threads.

EnterCriticalSection(Lock: TRTLCriticalSection) - The name of this procedure is identical to Windows API. You must think carefully where you place these and you always follow this up with an exception handling block.

LeaveCriticalSection(Lock: TRTLCriticalSection) - The name of this procedure is identical to Windows API. You must ensure that this procedure is called finally in your Locking/Unlocking block. There is one exception to this rule but you really need to keep track of locks. In the event that a method can take long periods of time it is conceivable that you would unlock the thread, if you know that an operation is going to take a significant amount of time, and re-lock it again. In that event, just make sure you handle all exceptions so that the Lock is eventually unlocked.

 Example Block that Performs Locking and Unlocking
   EnterCriticalSection(Lock);
   Try
     // Perform Code here
   Finally
     LeaveCritialSection(Lock);
   end;

Putting Threads To Sleep

Under Windows, making a thread go to sleep is best accomplished not by the Sleep method, rather the WaitForSingleObject method. Waiting for an event places threads in a stasis where there is little to no CPU cycles being used by the system until such time as an event was signaled or the specified timeout has been reached.

Since FPC did not offer a WaitForSingleObject method, the best way to to achieve highly efficient systems is to use Event driven Waits. Even though you know that no event will trigger an interrupt to that wait, it is best to use an Event. Therefore, in the example included, instead of using a Sleep(WAIT_MILLISECONDS) I included a technique which creates one Event Handle per Manager. Keep in mind most Lazarus applications, most likely will include only one instance of a Manager, but these exemplary units can be used in applications that contain multiple instances of the Manager.

Adding Data

Processing Data

Unit Files