|
Component Software's family of thread classes
A good set of C++ classes makes the use of threads less daunting. This
article describes such a set of classes, developed by
Component Software, Inc.
This set of classes is constructed so that you can write the majority of
your application code in such a way that you can compile them unaltered for
several operating systems. This is accomplished as follows:
The behavior of the classes is defined in a set of classes that are
independent of any operating system calls. Those classes are the ones that are
being used in the application code to deal with its threads. When your
application code is linked, the independent classes are supplemented with the
specific code for your specific operating system target. This
specialization code then makes the operating system-specific API calls to
implement the behavior that is defined in the OS-independent classes.
The creation of some of the classes must be done with factories, since the os-independent
classes are abstract. The factory is defined in an os-independent fashion, so
that these factories can be used in the os-independent code.
This article first describes the OS-independent base classes and their
interactions. It then briefly describes the supported OS-specific subclasses.
Thread and Runnable classes
"Thread" is a class that allows the creation of a thread, as well as control
over its run status (a thread can be in different states: established, running,
suspended, or terminated). The Thread class also provides a Sleep method for
convenience. The constructor of a thread requires a Runnable object. A thread will execute the DoStep method in
that
Runnable class. DoStep is called repeatedly until the
thread is terminated. Below are the public interfaces of Thread and Runnable
//***************************************************
class Thread
{
public:
Thread ( Runnable* rr );
virtual ~Thread();
virtual void Resume () = 0 ;
virtual void Suspend () = 0 ;
virtual void Terminate () = 0 ;
virtual bool IsTerminated () = 0 ;
virtual void Sleep ( int mSec ) = 0 ;
virtual void AssignToken ( TokenDepot* t ) ;
virtual void DeassignToken ( TokenDepot* t );
};
//***************************************************
class Runnable
{
public:
virtual ~Runnable () {}
virtual void DoStep () = 0 ;
};
|
Guard and TokenDepot classes
The TokenDepot serves as the holder for a Token. The purpose of the token is to
allow a maximum of one thread to obtain the token at a time. A token is obtained by putting the
TokenDepot into a Guard. When the Guard is destructed, the Token becomes free
and can be claimed by another thread. The public interfaces of the Guard
and TokenDepot are shown below.
//***************************************************
class TokenDepot
{
public:
//needed to call subclass destructor
virtual ~TokenDepot () {} ; };
//***************************************************
class Guard
{
public:
Guard ( TokenDepot& dep );
~Guard ();
private:
TokenDepot& m_dep ;
};
|
"Guard" is a concrete class, and can therefore be
constructed directly as an object. A Guard is typically established as a
temporary variable that encloses the instructions dealing with variables,
which are accessed by multiple threads. The example
below illustrates this use:
//*****************************************************
void f ()
//this function is executed by thread 1
{
//variable m_v is shared by multiple threads.
{
Guard g ( dep ); //dep is a Token Depot.
//dep now
owned by g, until g is destroyed..
int v = m_v ;
//this instruction is
executed knowing that
//m_v is not changed by any other thread.
//all threads must enclose
//access to m_v with a Guard ( dep )
//to make this work.
} //g is destroyed here, releasing dep.
//dep
can now be claimed by another thread.
}
//*****************************************************
void SubClassOfRunnable::DoStep ()
//this function is executed by
another thread
{
{
Guard g ( dep );//same dep as in f ()
m_v = 1 ;
}
}
|
Factories
The Thread and TokenDepot classes are abstract.
Os-dependent implementations must be provided. The Thread factory is defined as
a class ThrdSvc. To get a pointer to the current ThrdSvc, use GetThrdSvc. For
the TokenDepot, a simple function MakeTokenDepot is used as factory.
The thread class factory has been chosen to be a class so
that the os-independent code can be executed with different factories, even in
the same program. The most important application of this feature is in testing:
Since threads have an indeterminate timing relationship to each other (at least
in the general case), it is difficult to write deterministic test cases. To
solve this problem, special Thread factory classes with deterministic behavior
are installed during testing. Details are outside the scope of this article.
The factories are defined as
follows:
//*****************************************************
class ThrdSvc
{
public:
virtual ~ThrdSvc (){}
virtual Thread* MakeThread ( Runnable& r ) = 0 ;
};
//*****************************************************
ThrdSvc* GetThrdSvc ();
//*****************************************************
TokenDepot* MakeTokenDepot (); |
A simple example
Given the above classes, a simple example can be
constructed to show their use. Note that this example code builds the
functionality of the application without any OS-specific API calls. This code
can be run without any modifications on MS Windows platforms and on Unix
platforms that provide a pthread library.
The example prints out the time once a second until the
user hits the enter key on the keyboard.
The example works with two threads: the
first thread (that enters the function main) waits for the user to hit the
enter key; the second thread displays the time every second on the
terminal.
The second thread is created in main and associated with class
SecondThread ( a subclass of Runnable). SecondThread's DoStep method is
executed by the second thread.
#include
#include
#include "thread.h"
//*****************************************************
//create a subclass of Runnable. Important is to implement
//DoStep.
//to have access to a thread Sleep function, a member m_myThread
//is provided.
class SecondThread : public Runnable
{
public:
SecondThread () : m_myThread ( 0 ) {}
void YourThreadIs ( Thread* t ) { m_myThread = t ;}
private:
void DoStep ();
Thread* m_myThread ;
};
//*****************************************************
int main ( int argc , char** argv )
{
InstallOSThrdSvc ();
SecondThread sth ;
Thread* th = GetThrdSvc () ->MakeThread ( sth );
sth .YourThreadIs ( th );
th ->Resume ();
getchar ();
th ->Terminate ();
delete th ;
return 0 ;
}
//*****************************************************
//Implementation of SecondThread::DoStep
//is called repeatedly by
//its associated thread until thread is terminated.
//method will print time to display,
//and then wait for 1 second before
//returning
void SecondThread::DoStep ()
{
time_t t ;
time ( & t );
printf ( "current time: %s", asctime ( localtime ( & t )));
m_myThread ->Sleep ( 1000 );
}
|
Complementary code needed to connect to the platform API.
The os-independent classes can only do the job when the
required os-specific code is linked. The os-specific
code is supplied in subclasses of the os-independent classes. At link time the
appropriate os-specific files are compiled and linked with the os-independent
files to build the executable.
The OS-specific implementation of the ThrdSvc factory must
be created with the call to InstallOSThrdSvc () which is a function that must be
implemented by the OS-dependent code. That function must put a pointer to the
OS-specific thread service into the global variable activeThrdSvc.
The public declarations for the OS-specific subclasses of Thread
and TokenDepot for MS Windows and for the Pthread API are as follows:
MS Windows subclasses:
//*****************************************************
class W32_Thread : public Thread //, public CWinThread
{
public:
W32_Thread( Runnable& rr );
~W32_Thread();
//implementation of the Thread interface
void Resume () ;
void Suspend () ;
void Terminate () ;
bool IsTerminated () ;
void Sleep ( int mSec );
unsigned threadId ();
};
//*****************************************************
class W32_TokenDepot : public TokenDepot
{
public:
W32_TokenDepot ();
~W32_TokenDepot ();
bool GetToken () ;
void ReturnToken () ;
};
//*****************************************************
class W32_ThrdSvc : public ThrdSvc
{
public:
~W32_ThrdSvc () {}
Thread* MakeThread ( Runnable& r )
{
W32_Thread* r1 = new W32_Thread ( r ) ;
return r1 ;
}
};
|
Posix subclasses:
//*****************************************************
class LxThread : public Thread
{
public:
LxThread( Runnable& rr );
~LxThread();
//implementation of the Thread interface
void Resume () ;
void Suspend () ;
void Terminate () ;
bool IsTerminated () ;
void Sleep ( int mSec ) ;
};
//*****************************************************
class LxTokenDepot : public TokenDepot
{
public:
LxTokenDepot () ;
~LxTokenDepot () ;
bool GetToken () ;
void ReturnToken () ;
};
//*****************************************************
class LxThrdSvc : public ThrdSvc
{
public:
Thread* MakeThread ( Runnable& r )
{
return new LxThread ( r ) ;
}
}; |
Source code
Source code is provided for the above example. Also
provided is an implementation of the Thread and TokenDepot classes for MS Windows
and PThreads. Also provided are project files for Visual C++ and Unix autotools.
These files are made available to you under the terms of the Open Source Software license.
Download the source in zip or
gzip format. Instructions for compilation and
run are provided in the README file.
|