Features


Executing a Class Member in Its Own Thread

Allen Broadman and Eric Shaw

Creating a separate thread to execute a member function call is a messy business that's often necessary. It's a task well worth encapsulating.


Overview

When doing multithreaded C++ programming under Microsoft Windows NT/9x, it is often necessary to make a non-static class member function run in a newly created thread of execution. The Win32 API does not support this mode of operation directly. This article describes a technique that lets a client make a single function call that forces a class member to execute in its own new thread. Although the implementation shown is specific to Microsoft Windows, the general issues are relevant in any environment supporting threads. The technique can be ported to other operating systems, using their native multi-tasking primitives.

The Win32 API provides the function CreateThread [1], which allows the programmer to provide a user-defined function that is run by Windows in a newly created thread of execution. Here is the declaration of CreateThread, as it appears in the Microsoft Visual C++ version of the header winbase.h:

WINBASEAPI HANDLE WINAPI
CreateThread
   (
    LPSECURITY_ATTRIBUTES
       lpThreadAttributes,
    DWORD dwStackSize,
    LPTHREAD_START_ROUTINE
       lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    LPDWORD lpThreadId
   );

lpStartAddress holds the address of the user function to be executed. Stripping away the macro definitions behind LPTHREAD_START_ROUTINE reveals that the type of lpStartAddress is just a function that takes a single typeless 32-bit parameter and returns a 32-bit unsigned integer. This requirement is easy to satisfy with global functions, but can be cumbersome for C++ class member functions. A member function's address often cannot be used directly with CreateThread because the member's hidden this pointer makes its function signature incompatible. The rest of this article discusses scenarios when this problem occurs and presents two different solutions to the problem.

An Example Scenario

For many object-oriented C++ programs, it is more direct to have class member functions run in their own thread than to use global functions. But when does a class member need to run in a separate thread? A typical example occurs when client code must call a member function that provides guaranteed return time, yet must perform a potentially lengthy task such as a database access or remote procedure call. One way to guarantee response time to the caller is to have a public member function queue the caller request and return immediately. A private member function in a separate thread of execution can then process the queued requests, dispatching accordingly for each queued item. Here is an example of such a class:

class Task {...};
     
class Processor
{
public:
   // client initiates the task
   void submitTask(Task t)
private:
   // runs in a new thread
   unsigned processTasks(void *data)
     
   // holds client task requests
   std::queue<Task> _tasks;
};

In this class, submitTask will insert the requested task onto the _tasks queue and return immediately, thereby supporting clients with strict timing requirements. To handle the tasks, processTasks needs to run in a new thread in which it can continually process the _tasks queue and block when the queue is empty. Using an additional thread here reduces the return time of submitTask down to only the overhead necessary for copying and queuing the task information. Note that the overall processing time to handle tasks is not reduced by the additional thread — only the return time to the caller of submitTask is reduced [2].

Class member functions usually contain a hidden this pointer argument, which makes the member signature incompatible with the one required by CreateThread. For example, the true signature of processTasks (disregarding name-mangling) would probably look as follows:

unsigned
processTasks(Processor const *this,
   void* data)

which is incompatible with CreateThread. One common solution under Win32 is to make processTasks a static class member which has no this pointer. This has drawbacks, the most painful being the loss of access to non-static class data. Ideally, the member function should run in a new thread, but still maintain all the advantages of remaining non-static. We have seen various solutions that involve frameworks of classes supporting very fine control of the threading, but for many situations a simpler solution is adequate. Our solution encapsulates the creation of a new thread and the execution of the desired member function on that thread, but leaves the issues of data protection, thread suspension, termination, etc. to the class member implementers.

Two Solutions

Two different solutions are provided. Both use a global function called runInThread that encapsulates the operating-system specifics of the thread creation. The function is used by clients as follows:

Foo f;
runInThread(f, &Foo::member,args);

Executing this code will result in the execution of the non-static Foo::member within a new thread. The client can retrieve the handle and/or thread id of the new thread using default output arguments.

The first solution is supplied in thread1.h (Listing 1). It relies on forcing a compiler into interpreting a member function with no parameters as a global function taking a single void* parameter. It's then possible to create a new thread using the member function address and to pass the member an object this pointer directly via the void* thread argument of CreateThread.

This first version of runInThread was implemented and tested under Microsoft Visual C++ 6.0. It is short and simple, and works as expected — the member function is called directly by the operating system. However, this solution has a number of problems:

1. The member function signature is required to use the WINAPI calling convention (__stdcall in MSVC) which is not always possible. Also, this solution will not easily work for const member functions because const is a semantically significant part of the class member signature.

2. Using the void* thread argument to pass the object this pointer prevents the member function from taking any parameters, which is overly restrictive.

3. The most serious problem is that this implementation assumes (incorrectly) that non-static member functions will always receive their this pointer as the first argument on the call stack. Although this may be very common in practice, the C++ Standard does not enforce such behavior. A compiler vendor is free to use a different convention, for example placing the this pointer into a CPU register.

The code in thread2.h (Listing 2) shows a second solution that solves these problems, although at the cost of some increased complexity. The major changes are:

1. The runInThread function is replaced by the template class ThreadableMember which encapsulates a member function that will run in a new thread. ThreadableMember uses private variables to hold a reference to an object and the address of a member function to be executed for that object, both of which are supplied in its constructor.

2. The user-defined member function signature is made into a template parameter (MemberFunc) to address the issues related to WINAPI and const vs. non-const members.

3. The user-defined member function can now take a single strongly typed parameter. A new template parameter called Param is added to hold the type of this single argument.

ThreadableMember works by using a private static member function (ThreadFunc) that matches the function signature that CreateThread expects and which is responsible for executing the client-defined member function. Since ThreadFunc is limited to accepting only a single void* parameter, a nested struct (ThrdArg) is used to package all the data that ThreadFunc needs to execute the object member.

The run method creates a heap-based ThrdArg struct, copies the required data into it, and then calls CreateThread to start the new thread, using ThreadFunc as the function address to execute. ThreadFunc's job is simple — it unpackages the data from ThrdArg and executes the member function, passing it the client-provided parameter. An auto_ptr is used because it is ThreadFunc's responsibility to free the heap-allocated ThrdArg structure.

One nuisance with the class-based solution is that the class template parameters must be provided explicitly by the client because they are never automatically deduced [3]. This can be messy, especially since it involves pointer-to-member types. To solve this problem, runInThread is reintroduced as a global template function. It provides compiler deduction of the template parameters and simplifies usage of the class. Examples of both types of client usage are provided in test.cpp (Listing 3).

Issues

Allowing clients to force a non-static class member function onto a new thread can be dangerous. Multithreading requires great care in the protection of data, and class methods must be built with this in mind. Forcing a class member onto a separate thread from the other members of its class could result in catastrophic failure if the class implementers have not taken the proper precautions. At our company, we use runInThread within a public member function to force another (usually private) member function into a new thread, reducing client awareness of the class's threading usage.

Another issue is that runInThread forces the client to use default values for thread security and stack-size. Also, it is not possible to create the new thread in a suspended state. If this type of control is needed, default arguments can be added to ThreadedMember::run and to runInThread and passed on to CreateThread.

Acknowledgements

The authors wish to thank Andrei Alexandrescu for reviewing the initial implementation and for suggesting many improvements that were incorporated directly into the second version.

Notes

[1] Unfortunately, CreateThread is often misused. It is a low-level operating-system service that has no knowledge of your application's specific environment. It basically just creates a thread and executes the user-supplied function. A potentially serious problem for C++ applications is that the inherited Standard C runtime library doesn't know (or care) about threads. Assorted bugs and failures can occur if your multithreaded application uses a single-threaded C runtime library. The culprits are usually global data (such as errno) and functions that use global data for intermediate results (such as strtok). MSVC provides _beginthreadex, which initializes the C runtime library to replace usage of global data with thread-local data. You should check your compiler documentation to replace CreateThread with an appropriate wrapper function.

[2] On a single CPU machine, adding threads of execution will usually increase processing time because the CPU instructions required to schedule and switch between threads are not negligible. Perceived performance can often be improved if a user interface is involved. On a multi-CPU machine, true parallel processing is possible, and overall processing time can be reduced if threads are used carefully.

[3] For information on template parameter deduction, see Bjarne Stroustroup, The C++ Programming Language, Third Edition (Addison-Wesley, 1997) Section 13.3.1.

Allen Broadman works as a senior designer and C++ developer for Micromodeling Associates in New York City. His work focuses on real-time systems design and implementation. Allen authored the article and wrote the ThreadableMember class template. He can be reached at broadmana@mmanet.com.

Eric Shaw is a C++ developer at Micromodeling Associates in New York City. His work there focuses on systems programming, especially networking using sockets. Eric's idea to run class members in a new thread by using a single function call got everything going and he developed the first solution presented. Eric can be reached at shawe@mmanet.com.