Multithreading Do's and Don'ts


The behavior of kernel threads from platform to platform is reasonably consistent. With this in mind, I present some general advice on writing multithreaded applications:

Avoid Static Variables

Avoid static data if at all possible. Since static data is not stack allocated, each thread that makes a call to your function will be accessing the same piece of memory. If you must use statics, wrap them with mutex semaphores, or implement them as per-thread data areas.

Heap Allocate All Thread Arguments

The prototype for thread entry points usually contains a void* argument. You absolutely must heap allocate the reference of this argument, and, of course, destroy it upon termination of the thread. Consider the consequences of the following:

void thread( void* );
func() {
int arg = 5;
_beginthread( thread, NULL, 8192, (void*) &arg );
}
Obviously, the automatic integer passed to the thread will shortly fall out of scope and thus be deleted.

Wrap Globals with Mutexes

It is important to lock access to any resource that could be accessed by two threads at once. Failing to do so can create odd race conditions, intermittent crashes that are not traceable with a debugger, and a host of other run-time anomalies that will have a development staff scratching heads for weeks. This rule holds true for class variables as well, if more than one member function can be called at a time.

When placing mutex semaphores around class variables, take care to lock no more than what you really need to lock. Consider this stack class:

class intstack {
    int st[ 500 ];
    int pos;
public:
    void push(int ) {
    mutex( "intstack" );
    //...
    }
int pop( ) {
    mutex( "intstack" );
    //...
    }
};
This class should operate with no disastrous effects (i.e., no hangs, crashes, or corrupted data). The mutex will properly lock the class data members st and pos from concurrent access. But, the class may take a serious performance hit due to a side effect: all threads that use an instance of intstack will block. This is because this above approach makes no effort to distinguish which instance of intstack needs its resources protected. Some kind of naming system is needed. A quick fix might be to name the stack:

class intstack {
    int st[ 500 ];
    int pos;
    string localnm;
public:
    intstack( const string& name ) : localnm( name ){};<
    void push( int ) {
        mutex( localnm + "intstack" );
        //...
    }
    int pop( ) {
    mutex( localnm + "intstack" );
    //...
    }
};
This would give the differentiation necessary to make sure only calls to a given instance of intstack block. (Of course, naming all the stacks the same would lead to the original problem. You might choose to derive a name automatically from a time stamp or other pseudorandom source.)