Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Win32 Multithreading Made Easy


August 1999/Win32 Multithreading Made Easy

Win32 Multithreading Made Easy

John Harrington

Multithreading logic is hard to write and hard to maintain. So keep it simple and separate.


Introduction

This article describes a multithreading framework suitable for C++ Win32 programming. The code does not require MFC (Microsoft Foundation Classes) to run. It is written directly to the Win32 API. The code is based on an article by Russell Weisz, titled "First Aid for the Thread-Impaired: Using Multiple Threads with MFC." [1]

A multithreading base class is described in Weisz's article. The implementation requires MFC. I was working on a heavily threaded application that made using Weisz's implementation difficult because my application was not MFC based. I knew it was possible to use MFC from a console interface [2], but that did not appear to be an easy solution. Using MFC would require too many modifications. I thought a better approach was to take the idea presented by Weisz and port it to the Win32 API.

The Framework

The framework consists of three classes broken out into two header files, ThreadableObject.h and MultiThread.h.

ThreadableObject is a lightweight base class that ties user code to the framework. Use of ThreadableObject keeps user objects small and draws a clean distinction between application code and the threading framework.

Multithread.h defines two classes, MultiThread and MultiThreadState, and global function MultiThreadImpl. Users interact with the thread through class MultiThread. MultiThreadState and MultiThreadImpl are declared at file scope only out of necessity. They are implementation details of the framework.

Creating a separate thread is a two-step process. First, derive your class from ThreadableObject and provide an implementation for the pure virtual function ThreadableTask. Second, create a MultiThread object and pass it a pointer to your derived class.

Once the thread is created MultiThreadState acts as repository for the thread's status. Users change the repository by making function calls on class MultiThread. The thread function, MultiThreadImpl, periodically retrieves information from the repository and responds accordingly.

An Example

I've created an example to demonstrate the framework. I describe the code in reverse order, from what it looks like on the outside to how it is implemented.

Figure 1 shows a multithreaded application. Class Derived inherits from the multithreading base class. The first loop creates nSize number of objects, in this case one for each letter of the alphabet.

The second loop creates a thread for each object and starts it. The Initialize method requires two parameters, a pointer to the ThreadableObject and the thread's cycle time, given in milliseconds. The cycle time is approximately equal to the time between successive calls to the ThreadableTask function. The thread model is described in more detail below.

There are two other parameters that could be passed to Initialize. They are the thread's stack size and a security attributes parameter. I implemented the stack size as an optional parameter. I choose not to implement a security attributes parameter because the C++ code provides access control for me. Outside objects do not have direct access to the thread's state. Some applications may require this parameter. For those that do, it can be passed through the Initialize method and to the underlying call to CreateThread.

The last loop prevents the program from exiting until all threads have completed. This code was originally required, but statements added later to MultiThread's destructor make it optional. I've left the code in for clarity.

Figure 2 shows the code for class Derived. It inherits from ThreadableObject and implements ThreadableTask, the one virtual function it declares. In this case, the function prints a character to cout. The function terminates when 100 characters have been printed. Variable dwReturnCode becomes the thread's exit code and can be set before returning from ThreadableTask for the last time. See the section "The ThreadableTask Function'' for more details.

Figure 3 shows the code for classes MultiThreadState and MultiThread. MultiThreadState encapsulates the data for a thread. It has a limited public interface but declares MultiThread and MultiThreadImpl as friends so they can conveniently access it. Users of class MultiThread do not need to know about MultiThreadState. It is declared at file scope so MulitThreadImpl can use it. See MultiThread::Initialize.

Run the example and you will see the output looks something like

aaaabbbccccccddeeeeee....

Each thread runs until it is preempted by the operating system. The number of successive letters that are identical is arbitrary. For a more even distribution of letters, uncomment the second return statement in Derived::ThreadableTask and run the example again.

The Threading Model

The thread model is summarized by the enumeration that appears in MultiThreadState. A thread created by this framework can be in one of the following states: UNINITIALIZED, INITIALIZED, WORKING, SLEEPING, DONE, SUSPENDED, and RESUMED.

MultiThread objects are UNINITIALIZED immediately after construction. The Initialize method creates the thread, changes its state to INITIALIZED and suspends it until the user calls Run. At this point, CreateThread has been called, but the first thing the thread does is wait for an object that is signaled when the user calls Run.

Once Run is called, the thread state changes to SLEEPING. It remains there for at least dwCyleTime milliseconds. If your thread must start working immediately, follow the call to Run with a call to DoWork. This will signal the event the thread requires to continue.

The thread state alternates between SLEEPING and WORKING (approximately) every dwCycleTime milliseconds until the ThreadableTask function returns false. When this occurs the thread, and the task to perform, are finished. The thread's state is changed to DONE.

Two additional states listed in the enumeration are SUSPENDED and RESUMED. They are implemented in terms of the Win32 functions SuspendThread and ResumeThread. In this implementation, I return the thread's current suspend count instead of the pervious suspend count. In addition, a resumed thread will stay in the RESUMED state until the next transition occurs, however long that may be. The thread stays RESUMED for the remainder of the state it was suspended in, whatever state that may be. The Win32 documentation lists other thread function calls that can be implemented (or changed) in a similar fashion.

The ThreadableTask Function

ThreadableTask represents the work to be done. ThreadableTask's pure virtual function is what brings the framework together. MultiThreadImpl calls this function through a ThreadableObject pointer, so the correct function is always called. Since ThreadableTask is implemented in the derived classes, the code can access all of the derived classes member functions and data. In effect, you take the work you want done and whatever data it requires, wrap it up in an object, and execute it inside another thread.

ThreadableTask takes a DWORD pointer as a parameter and returns a bool. The function returns true while there is work to do, and false when the task is completed. The parameter becomes the thread's exit code. Its use is optional. If no return code is given, the default value is zero.

The most important thing to keep in mind when implementing ThreadableTask is, "How responsive to the outside world do I want my thread to be?'' In the simplest cases, a thread runs "unattended'' until it is finished. In practice, this rarely happens. At a minimum, someone will want to cancel a thread if it is doing work that is no longer necessary.

Threads created with this framework can respond only to certain events, such as cancellations and changes in cycle time, between transitions from WORKING to SLEEPING. This is the point where MultiThreadImpl updates its state information. The more calls made to ThreadableTask, the more in tune your thread will be with the outside world.

The original example was not responsive at all. Each thread made one call to ThreadableTask. It was up to the operating system to preempt a thread and schedule another one for CPU time. The modified example was extremely responsive. It could adapt to new state information after each letter was printed. The level of granularity is entirely up to you.

Two other points to keep in mind:

1. ThreadableTask will always be called at least once. This is true even if you issue a Run command and immediately follow it with a RequestStop statement.

2. ThreadableTask must return at least once. Code in MultiThread's destructor explicitly waits for the thread to finish.

Stopping a Thread

Stopping a thread in this framework is a joint venture. RequestStop simply sets a flag inside MultiThreadState. The thread will not act on this information until there is a transition from WORKING to SLEEPING. The frequency and distribution of these transitions is controlled by the structure of ThreadableTask.

Synchronization

The execution of other threads can be postponed until a given thread terminates by calling WaitUntilDone. Each thread has an event that is signaled when the thread terminates. The event is manually reset so multiple threads can wait on a single thread to finish. Be careful to avoid situations where a MultiThread object is destroyed before all of the threads that are waiting on it can be notified. Make sure the MultiThread object stays in scope long enough to activate all the waiting threads.

Each thread in the example was an independent entity. There was no interaction between or among threads. In practice this is rare. Synchronization code must be added to either the derived class or the ThreadableTask function to coordinate the activity of cooperating threads.

Other Issues

One class that is used but not shown in the listings is MutexLock. This class is a simple wrapper for a mutex handle [3]. The timeout on the WaitForSingleObject is INFINITE.

MultiThread::Initialize uses the Win32 function CreateThread. I could instead have used functions BeginThread or BeginThreadEx. An advantage of these functions over CreateThread is that they are less prone to memory leaks when certain C runtime functions are used. My next version of this code will probably use one these functions.

While I am discussing future versions, I want to point out the biggest advantage of this framework. It separates the task you want done from the threading code that calls it. MultiThreadImpl represents one possible thread model. You could easily create another model for a specific situation and tie a ThreadableObject derived class to it.

Conclusion

This article described an object-oriented framework for Win32 that encapsulates threads. The code was based on an article presenting a similar framework for MFC. The framework cleanly separates thread specific code from the task the thread is performing.

References

[1] Russell Weisz. "First Aid for the Thread-Impaired: Using Multiple Threads with MFC." Microsoft Systems Journal, December 1996.

[2] Mike Blaszczak. Professional MFC with Visual C++ 5 (Wrox Press, 1997).

[3] Bjarne Stroustrup. Design and Evolution of C++ (Addison-Wesley, 1994), Section 8.3.2.2

John Harrington has a B.S.E.E. from MIT and an M.S. in Computer Engineering from the UCSD. He works for Nuera Communications in San Diego, a provider of high-quality packet voice communications systems and technology for voice/fax/data/video networking over IP, frame relay, and circuit-switched networks. He can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.