up to Magazine | up to category | to top of site

This essay has a file date of January 8, 1997. I don't know where it appeared or where #1 is. I found it along with an early backup copy of the threading book, which was lost in a disk malfunction. So it may be a backup of part of a larger collection which has been lost.

Multithreading #2

In the previous essay, I showed how to start a thread under Win32, exploring the low-level API calls to do so, the ramifications on the run-time library, and points and pitfalls. In that article, I presented a pattern for coding thread interfaces in C++.

In this article, I automate the pattern and introduce a new tool for the purpose.

There are a lot of threading issues to explore, and mutual-exclusion techniques is high on the list. However, further exploration of the subject will require lots of code which creates threads. So it makes sense that this fundamental process is made as simple as possible before continuing.

Detached Function Calls

Normally, invoking a function using the function’s name followed by a list of arguments specifies a single-threaded synchronous call and return. That’s the way it’s been for decades, and a concept that is so ingrained in our minds that it’s hard to imagine anything else.

For example, the statements

foo (1, 2, 3);
bar (x, y, z);

means that foo will be executed, and then after foo is completed (and not before!) bar will be executed.

The alternative would be a way to execute foo in the background but don’t wait for it. Meanwhile, continue with bar. You might use syntax like this:

detach foo (1, 2, 3);
bar (x, y, z);

In this hypothetical language extension, the detach keyword is followed by a statement which is run in the background, as another thread. Some languages provide this kind of feature, or make it possible to write such a feature in the language. Unfortunately, C++ is not one of them.

Perhaps the next best thing would be to write:

detach_foo (1, 2, 3);
bar (x, y, z);

Following the general coding pattern presented last time, the way of creating a thread starting with some function (say, foo) is:

It doesn’t look too difficult, but there are a few snags. Specifically, foo would need to be modified to signal the end of critical initialization. That kind of defeats our purpose of neatly encapsulating the detachment of any old function that we happen to want to run in the background.

But, why do we need to note the end of critical initialization at all? If this is a simple function running in the background, the main thread won’t need to interact with it once the thread is started. The critical initialization stage is needed so the new thread can then be used (whatever that means) by the spawner. For example, the new thread creates a window, and the parent thread posts messages to that window. If the main thread resumes and tries to post a message before the child thread has created the window, the program will fail.

But a function that simply starts running and doesn’t get controlled by other threads should not care. For example, printing a document, reballancing a tree, or doing garbage collection are things that can be started, run by themselves, and don’t need any further attention from the main program.

Or does it? The printing thread, for example, is never controlled by another thread. It accesses data shared by the main program (the thing to be printed), but we expect that locking mechanisms designed into that shared data will take care of this thread’s needs. But there is still a hole in the scheme.

Suppose we start a background print job on document X. The thread is slow in starting up, for whatever reason (e.g. started with a low priority, or blocked doing other preparation work) and the main program closes the document X. Because the print thread was tardy getting started, it has not yet incremented X’s reference count. The main program closed the document and freed the memory. Now the print thread has an invalid pointer. So, it seems that there is critical initialization after all!

Another problem is the passing of parameters. If the spawning thread doesn’t yield to the new thread to allow critical initialization, then the spawning function can’t pass any local variables to the thread-start function. This would complicate the means of getting information to the detached function. It’s not impossible, but it’s more work.

So, writing a detach_foo wrapper without a critical-initialization event would complicate the implementation because of parameter passing, but worse would only be useful in limited situations, and only guaranteed to work without synchronization problems in even more limited cases.

It sounds like functions such as background printing really do need to be designed to run as separate threads in the first place, rather than simply being wrapped by a detachment mechanism.

Live Objects

In C++, a more natural representation of a background task is an object, not a function call. This is in fact how the example in the previous installment worked:

DWORD __stdcall thread_start (void* raw_p)
  {
  thread_args* p= static_cast<thread_args*>(raw_p);
  try {  //required under Borland, not needed under Microsoft
     p->retval= new C (p->x, p->y);
     SetEvent (p->critical_initialization);
     p->retval->run();  //doesn't return 'till the background task is finished
     }
  catch (...) {}
  return 0;
  }

The existing code being wrapped is broken into two parts: the constructor and the run() member. Between those two steps, the thread_start function signals that critical initialization is complete. The class is written so that the constructor takes all the arguments (to make its own copies of data, as needed) and sets up whatever mechanism will be used to communicate with the thread. In general, it is a good constructor that puts its object in a usable state. The run function, which doesn’t return ’till the job is done, starts the actual work. Meanwhile, SetEvent has already told the spawning thread to resume.

Modeling background activities as objects has a number of advantages. The means used to communicate with the worker thread (if any) can be encapsulated with the member functions. Because of that, it’s handy to have the object’s pointer represent the task. The background task will naturally store its state information in member data. Inheritance or aggregation can be used to incorporate standard components for dealing with live objects, such as pausing and aborting.

So let’s generalize this idea into a pattern and see where it gets us. Again following the pattern we presented last time, to make a live object as an instance of class C:

This exactly describes how the program in the previous installment works. Since we don’t have to delve into just what class C does, this sounds like a good general mechanism for creating threads in C++.

Do it only once

The above recipe for a live object is a good “meta level” description of what each object will contain. However, C++ does not offer a code reuse mechanism that fits this pattern. The big problem is that the number of parameters needed for the constructor and for create can vary. Neither templates nor macros can handle an arbitrary variable number of arguments.

If we knew how many arguments there were, even if we don’t specify the type ahead of time, we could consider templates. So one solution is to have a different template for each supported number of arguments. For example, listing 1 shows a template for two arguments. A different template would be needed to handle three.

Listing 1 — LO_tem_2.h

This template is designed to be used as a base class, as it supplies the create member. However, the template needs to know what class is the final derived class, which means supplying it as a template argument along with the types of create’s (and the constructor’s) arguments.

class background_job :
      public LO_tem_2 <background_job, int, int> {
public:
   background_job (int, int);
   void run();
   //… other stuff
}

The derived class needs to supply a constructor and a run function, which are used by the generated create function. The template provides all the mechanism for creating a new worker thread and producing and activating the object within that new thread, and getting the pointer back to the caller.

background_job* p= background_job::create (3,4);

That’s easy! Listing 2 shows a full example. The output from listing 2, on a lightly loaded machine, will be:

New thead is started.
background_job constructed with parameters 3 and 4

back in main()
1 squared is 1
run is called.
2 squared is 4
background iteration 1
3 squared is 9
4 squared is 16
background iteration 2
5 squared is 25
6 squared is 36
background iteration 3
7 squared is 49
8 squared is 64
background iteration 4
9 squared is 81
background iteration 5
background iteration 6
background iteration 7
background iteration 8
background iteration 9
run is finished.

On a heavy loaded machine, the threads may interleave their output in a different order.

You can see that the main program does its work of printing a list of squares, and behind the scenes the time-consuming background operation is also running.

The main program may finish first (in this example, I know it will) so it needs to wait for the background process to complete before terminating the program. To meet this need, I added a member to the background_job class which waits. There is no such member designed into the Live Object template because the needs will vary from use to use. Mechanisms for a program to keep track of worker threads will be discussed in a later installment. Right now, we are still working on starting threads. We want to get that down pat before exploring the myriad of issues in using threads.

Listing 2 — LO_tem_test.cpp

Although it does achieve some measure of code reuse, and encapsulates all the machinery of creating a Live Object, it has its flaws. As pointed out before, it’s not flexible enough for general use. There is no way to parameterize it so create (and the constructor) can take any number of parameters. A different template, easily produced by copying and editing this one, would be needed for a different number of arguments.

Topsy-Turvy Hierarchy

Even if there were a more powerful template or macro mechanism to use, there is a subtle nit with using a base class to add this functionality. In the example program, class background_job is defined with public constructor and run functions. Arguably, these should not be used directly but called only by the create function and its helper functions, hence not defined as public.

But, this is a strange situation if you think about it. Normally, a class is extended by derivation. The protected section exists so that the new code can access things unavailable to the general population. Here, a class is being extended by a base class! That’s backwards, isn’t it? It’s the concept of a “mix in” class, but normally mix-in’s don’t know or care about other mix-in’s or subsequent derived classes. In this case, the mix-in knows about the class that will be derived from it—that’s the first template parameter. There is no built in mechanism that functions like protected but the other way around. In fact, a base class accessing members of its derived class may even be against your religion.

Better Protection

Two techniques to make the compiler better enforce strict control over what members are accessible would be to use a friend, or to add a dummy argument to the constructor and run functions.

The friend is easy to understand. These two functions could be made private if the mix-in class was declared to be a friend. That adds one line to my class.

class background_job : public LO_tem_2 <background_job, int, int> {
private:
   friend class LO_tem_2 <background_job, int, int>;
   background_job (int, int);
   void run();
public:
   void wait_for_completion();
   };

This is slightly intrusive, but it is totally optional and it’s not difficult to add. It’s a good solution for the mix-in presented in listing 1.

However, it could be better. This friend solution means that the mix-in can access anything in the background_job class. In this case, the mix-in class is already in the can, so to speak, so I won’t be accidentally accessing things I shouldn’t.

But in a more general case, it would be handy to give carefully controlled access to exactly those parts I want to grant access to. Suppose two classes in such a relationship (class A has “special access” to class BM) are both being developed and maintained. It is rather likely that the programmer working on A will access things in B that he shouldn’t, and that will hinder the maintainability of B. Once A is a friend of B, the door is wide open.

By analogy, we can put a lock on the door. We can make special functions in B that is A’s special back-door, distinct from other non-public functions. One way to do this is with a dummy parameter.

template <typename Class, typename arg1_t, typename arg2_t>
class LO_tem_2  {   //Live Object template for 2 arguments
protected:
   enum passkey_t { passkey };  //add this line
…….

class background_job : public LO_tem_2 <background_job, int, int> {
public:
   background_job (int, int, passkey_t);
   void run(passkey_t);
   void wait_for_completion();
   };

Now, the constructor and run function are indeed public. But, you can’t call them without an instance of passkey_t. Meanwhile, passkey_t is a protected member of the mix-in. So, that mix-in (and derived classes) can use passkey to call these functions. No other code can refer to passkey, so no other code can call these functions.

That's the end of the recovered file. That is an interesting digression in producing multiple controlled interfaces, but it never did show the final well-designed solution for a reusable module or present "the tool" which I suppose was a code generator.

So what am I using now? My launch_thread template is minimal (see it here), and it works by passing the object after you create it. So, the constructor is not called in the context of the new thread (that may or may not bother you) and it doesn't deal with critical initialization issues in passing parameters, but neither does it have a built-in and reusable place to put any critical initialization you might have.


Valid HTML 4.01!

Page content copyright 2008 by John M. Długosz