There is no question that multithreaded programming adds a layer of complexity to applications. With concurrent processing, debugging becomes more difficult, critical sections of code must be protected, and shared resources have to be managed. Of course, the trade-off is that by adding multiple strands of execution to an application, efficiency can be dramatically increased. From a programmer's perspective, the gains awarded by introducing concurrent execution often hardly seem worthwhile when compared to the headaches involved in dealing with threads mysterious things happen that hide when inspected under the debugger, or irreproducible bugs pop up at haphazard intervals, or worst of all, things do not run any faster.
Many of these annoyances can be avoided by localizing thread logic and carefully managing shared resources which is often much more difficult than it appears. By coupling the Command design pattern with a multithreaded application, one can perfect the nuances of multithreaded programming by creating a framework that encapsulates most of the multithreaded behavior and reduces development to application-level logic. Moreover, this mechanism allows you to develop in an open/closed manner, which means that none of the working thread code needs to be modified as you add more and more functionality.
The Command pattern is a design principle that allows you "to issue requests to objects without knowing anything about the operation being requested or the receiver of the request" (Gamma 233). At its core, the Command pattern is implemented as a base class that defines a pure virtual function, Execute(), which subclasses implement. The Execute() function in the concrete subclasses defines the specifics of the work to be done.
By tying this idea with a multithreaded framework that accepts Commands, one can develop multithreaded applications without having to deal with much of the complexity of multithreaded design. The framework follows a producer/consumer model that has worker threads as consumers. The producers produce various commands all derived from the Command class. When the producers hand these commands over to the worker threads, the threads need not know any details because of the polymorphic nature of the Command class. The workers simply call the Execute() function. Since the application logic is encapsulated in Command subclasses, this framework can adapt to perform any type of work and can be extended without impacting the code already developed.
There are a number of different thread libraries available, but this article focuses on Win32 threads, which are standard in Windows architectures. For more information on Win32 threads, refer to documentation on MSDN. An excellent resource for general threading questions is Pthreads Programming which actually concentrates on the POSIX pthreads library by Bradford Nichols, Dick Buttlar, and Jacqueline Proulx Farrell. Design Patterns, by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, is an excellent resource for more information on the Command pattern.
Developing a Multithreaded Program
What advantages does threading bring? Any time that efficiency is a factor, multithreading can result in a performance gain if two or more things can be done in parallel. Of course, there are alternatives to multithreaded programming, namely using multiple processes to perform work concurrently; however, threads have inherent advantages over processes. "The overwhelming reason lies in the single largest benefit of multithreaded programming: threads require less program and system overhead to run than processes do" (Nichols 13). Also, since threads share process space, communication and resource sharing between threads is more straightforward than over processes.
Thread creation in the Win32 model is done through the CreateThread() function. The function of this call is to create the thread. The function takes as a parameter a pointer to the thread function the entry point of the thread. The newly created thread will then execute in this function and will expire when the end of the function is reached.
Listing 1 is an example of thread creation with the CreateThread() function. As you can see from this simple example, there are two threads that are running which is evident by the interleaving output the thread created by the CreateThread() call and the thread that is running in main(). The WaitForSingleObject() function allows the caller to wait for the specified thread to complete before proceeding. It is essential in this case because the thread function takes longer than the operation running in main, and leaving it out would result in the thread not being able to finish before the program exits.
To gain more utility in multithreaded development, you need to use mutex variables to perform synchronization between threads. A mutex variable is a locking mechanism that protects shared resources from being accessed simultaneously. A mutex allows the thread that has obtained the lock to have exclusive rights to a resource or resources until that lock is released. Mutexes can be used to protect access to files, global variables, counters, or anything that needs to be held exclusively by one thread. The Win32 thread library provides a mutex variable type and functions to acquire and release the lock on mutexes by way of the CreateMutex() call and the WaitForSingleObject() and ReleaseMutex() functions, respectively.
Listing 2 is an example of thread synchronization using mutex variables. Because of the mutexing, instead of interleaving output as seen earlier, all the output from one thread and then all the output generated from the other thread is displayed. This is because one thread was able to obtain the lock on the mutex, leaving the other thread blocked until the mutex was released. When the mutex is released, the blocked thread is then able to obtain the lock and proceed.
In the examples so far, the CreateThread() call was made whenever a new thread was needed, but since thread creation does carry some overhead, it is quite common to create all the threads at initialization and have them wait for certain events to occur this approach is called a "thread pool." By utilizing a thread pool, the entire thread creation overhead is taken on at startup and no such performance hit needs to be taken during the course of the program execution.
A common implementation of a thread pool requires the use of another synchronization mechanism, event variables. Event variables allow threads to idly wait for events to occur without spending overhead polling for those conditions to arise. A thread pool uses event variables to alert sleeping threads to awaken and perform tasks. Since all the threads that are needed for program execution are created at startup, these threads often have infinite loops in their entry functions.
Listing 3 is an example of a simple multithreaded application that reads messages from a queue by using a thread pool. You can see that the threads initially go to sleep as there are no messages to be processed. When main pushes messages onto the queue, however, the threads are alerted to that event and start to pull the messages off the queue and process them which, in this case, is simply writing them to standard output. The call to sleep() in the thread function is there to artificially increase the message-processing time and to show how one thread can perform tasks while another thread is busy. When a thread finishes its task, it returns to an idle sleeping state, again waiting on the event variable to set it to work.
The Command Pattern
The Command pattern is fairly straightforward and only requires a few steps to implement. First, the basics of the pattern are implemented by way of an abstract base class whose interface includes a virtual function, Execute(). The subclasses will implement this function to carry on whatever task the class is specialized to perform this potentially includes the ultimate receiver of the command as well as the details of the task to be carried out. There are a number of known uses for Commands to support logging, to queue requests, and to support undoable requests but what is important to remember is that all Commands have the same interface and they hide the knowledge of the recipient from the requester. The Command class also lends itself quite easily to promote the open/closed principle, as extending functionality is minimized to adding not modifying more Command subclasses.
Listing 4 gives a short example of the Command pattern. The polymorphic nature of the Command class allows the caller to simply evoke Execute() without knowing about the internals of the derived Command classes. Of course, in this simple example, there really is no mystery as to what the Command subclasses are doing, but by taking the example a bit further by adding multiple threads, a more useful example can be developed.
Pairing the Command Pattern with Multithreading
By coupling the Command Pattern with a multithreaded server several advantages arise. Most important, all of the core processing logic is embedded within Command classes. That way, any additional functionality that needs to be added can be done by adding Command subclasses, without touching a single line of the already existing code. It is possible that Commands can be developed entirely isolated from the multithreaded logic, which would allow you to fully test and debug conventional logic without having to immediately enter into a multithreaded environment. A great advantage is gained by isolating the existing code base you eliminate the danger of introducing new bugs to already tested and deployed code. Additionally, because the threading logic can be developed separately and discretely without having thread-specific calls proliferate throughout the code, this logic can be tested, debugged, and perfected without having to rewire an entire application. This reduces the complexity of multithreaded programming to a single locale and it can be tuned and debugged at that single point in the code. This is extremely important on any reasonably sized project, especially one that includes a wide range of threading expertise.
As you can see in the example shown in Listing 5, the threads themselves contain no processing logic aside from calling the standard Execute() function on the Commands that they receive. Moreover, all of the threads share the same entry point and as such, almost all of the thread-specific knowledge is in that entry function. Commands typically contain the identity of the recipient; however, in this case, since the threads handle all of the Commands homogeneously, I found it clearer to extract that functionality and explicitly push Commands onto the queue in main. This would not be the case if I had needed specialized threads to take care of the work to be done, and in that case, the logic to pass the Command off to a particular thread or to push the Command on a specified queue would be encapsulated in the Execute() function of the class. Of course, the Commands in the example are very simplistic, but I hope that they are indicative of what types of things are possible and made easier by using the Command pattern.
To further extend the functionality of the Commands presented here, think of Commands as discrete packets of work. Virtually any type of work that may need to be done can be encapsulated in Command classes, and by wrapping all of the knowledge that is needed to carry out a specific task in a Command class, the framework presented here will take care of the rest.
This framework for doing multithreaded programming allows you to isolate much of the complexities inherent in this type of development. Depending on the specifics of your program domain, however, you may or may not have to add more synchronization mechanisms over worker threads or Commands. By starting with this structure and by using the Command pattern, you can add in and remove code without disturbing the rest of your application and, while it may not eliminate the difficulties surrounding multithreaded programming, it can hopefully diminish them and help you avoid some of multithreaded programming's most common pitfalls.
Gamma, Erich, Richard Helm, Ralph Johnson, and John Vlissides. Design Patterns. Addison-Wesley, 1995.
Nichols, Bradford, Dick Buttlar, and Jacqueline Proulx Farrell. Pthreads Programming. O'Reilly & Associates, 1996.
James Pee is a software engineer at G Systems Inc., a system design and integration company specializing in test and measurement, data acquisition, and control applications (www.gsystems.com). He can be reached at email@example.com.