Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Job-based Multithreading

12 May 2002 1  
A threadpool class supporting job objects which runs on (almost) all Windows versions

Sample Image - CJobManager.jpg

Introduction

Multithreading and the necessary synchronization can sometimes be quite challenging. Windows 2000 brings some new concepts into play which doesnt make it really much easier to cope with this topic. Processes, threads, fibers, jobs and whatelse is available to "help" writing applications that do many things at the same time.

Most less experienced developers get lost while trying to figure out what to use and when. After finally having figured out how to multithread, the joy of debugging a multithreaded application starts. Things start to get really funny when you finally have a multithreaded solution up and running and it deadlocks on a multiprocessor machine. Never assume that your multithreaded application behaves correctly until it runs sucessfully on a multiprocessor machine.

There are many factors that can cause your concept to fail: compiler optimizations due to missing or incomplete variable declarations (e.g. missing volatile), deadlocks because of unexpected execution behavior, simple logic errors and many others.

Job-based Multithreading

In many cases a solution that creates one thread for each job could just blow the system and achive the opposite effect: a slowdown due to too many threads running. Imagine a class to enumerate and process files and directories. A simple implementation could just launch one thread for each directory found to speed up processing.

The idea is good, but can you imagine what will happen if your customer has limited throughput on disk I/O but his disc-controller is quite good in caching? The result will be an explosion of threads because directory and file enumeration is fast because of the disc cache, but processing of the files contents is slow as a result of the limited disc I/O.

A better solution in most cases is a limited set of threads (known as a thread pool). A queue of jobs feeds the threads with work. While Windows 2000 and Windows XP support thread pooling and jobs, NT4 and Win9x do not. Which makes it just a bit more difficult to write cross-Windows applications.

The CJobManager class brings the advantages of a thread pool and jobs down to the C++ level and is fully cross-windows (although I actually did'nt test it under Win9x).

CJobManager provides a very flexible interface to be of use in most applications that "require" multithreading while still supporting GUI processing. In one of the comming articles I will present a more sophisticated approach with guarded execution and secure multithreaded object orientation.

But for now, lets stick with the simple implementation of CJobManager.

The Class

CJobManager defaults to be used as a singleton, thus has no public constructor/destructor. Your application gets a CJobManager object by calling the static memberfunction CJobManager::GetJobManager(BOOL bNewManager, int nThreads).

Anyway, by specifying TRUE as the first argument, a new instance of a CJobManager will be allocated and initialized. Be sure to save the returned pointer, as the function does not track created instances.

The most important function is CJobManager::AddJob(LPTHREADEDJOB pJob). The argument pJob points to a simple structure which carries all information needed to process a job. This structure is the point where you can extend the implementation of a job to accomodate your needs.

typedef struct tagThreadedJob
{
//... ctor/dtor implementation hidden here ...

    LPTHREAD_START_ROUTINE pFunction;
    PJOBMANAGER_FEEDBACK pfnFeedback;
    LPARAM lParam1;
    LPARAM lParam2;
    LPARAM lParam3;
    LPARAM lParam4;
    LPARAM lParam5;
} THREADEDJOB, * LPTHREADEDJOB;

Note: You can not remove a single job once it is queued.

The class implementation defaults to be non-terminating to its threads. This means any instance of CJobManager will wait for completion of the running jobs without interrupting the threads. You can however, specify that you definitly want the threads to be terminated when the application needs to, by calling SetTerminate(TRUE).

You can at any time increase or decrease the number of threads (default is eight) in a instance of CJobManager by calling SetThreads(nThreads). When in non-terminating mode, the function will wait for each thread to finish its job before removing it from the pool.

If your application wants to wait for completion of the jobs it can call CJobManager::WaitCompletion(BOOL bCurrentJobsOnly). WaitCompletion returns only after all jobs have been processed. It does allow message processing by calling the CJobManager::ProcessMessages() function.

When your application needs to poll for completion of the jobs it calls CJobManager::IsCompleted(). Together with CJobManager::Wait(DWORD dwMillisec, BOOL bGUIprocessing) you can construct wait loops to do some processing while waiting for completion of the jobs.

while(!CJobManager::GetJobManager()->IsCompleted())
{
    // do some processing here (update status, etc...)

    CJobManager::GetJobManager()->Wait(250, TRUE);
}

The CJobManager class also supports a feedback function that can be called from within a jobs processing function. The current job provides this function through Feedback(LPVOID pFeedback). This function will return TRUE when no feedback function is provided, else it calls the function and returns the return value. The intention of this feedback function is on one hand, to allow for real feedback on actual processed data which can be provided to the feedback function with the LPVOID argument, and on the other hand to allow ending the current job by returning FALSE.

Call CJobManager::SetFeedback(PJOBMANAGER_FEEDBACK pfn) to set a feedback function.

The Sample

The included sample uses a modified CFileInfo class from Antonio Tejada Lacaci to enumerate all files from a choosen drive. The sample starts processing at the root of the drive and adds a job for every found directory. These jobs are processed by the default eight threads. The processing can be interrupted at any time with the "Stop" button. Every 250 milliseconds the dialog updates the count of found files.

Note: the sample leaks memory when you close the dialog while its still running because some objects are not freed when terminating the CJobManager.

Compatibility

Tested under Windows XP, Windows 2000 and NT4SP6 on single and dual processor machines. UNICODE compatible but not included in this sample. VC6.

Update

May 14, 2002 - bug in WaitCompletion() fixed.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here