Introduction
First off, the "prerequisites" of this article: I give some brief examples of using a mutex and semaphore object, but I leave it up to the reader to understand exactly what they are. Also, you should have a good understanding of multithreading before reading this article.
"What makes this different than other thread classes?" you might ask. First off, it is portable (but you probably noticed that from the title) - but my favorite part is that it is efficient. Nowhere in this article or in my code will you use new
, delete
, malloc
, free
, etc... everything is done on the stack. I am still hard at work to make this class even faster, so check back every now & then for updates, assuming you enjoy the code.
Please let me know how you like this article & code and if you have any suggestion to make it better.
Features
- Pure virtual interface to easily convert any class to a threaded class
- Type abstractions for thread handles
- No code change needed between supported platforms
- Static functions for easy static thread creation
- Basic portable synchronization objects
- Tested under Win32 (95-XP), *Nix, and SunOS >5.7
- Should work on any posix-compliant system that support standard semaphores
The Thread Class
Here is a quick run-down of the functionality available to the user:
template < typename Thread_T >
class Thread
{
public:
typedef Thread_T & Thread_R;
typedef const Thread_T & Thread_C_R;
typedef Handle;
typedef void ( *Handler)( Thread_R );
protected:
Thread();
virtual void ThreadMain( Thread_R ) = 0;
static void Exit();
static void TestCancel();
static Handle Self();
public:
static int Create(
const Handler & Function,
Thread_C_R Param,
Handle * const & H = 0,
const bool & CreateDetached = false,
const unsigned int & StackSize = 0,
const bool & CancelEnable = false,
const bool & CancelAsync = false
);
int Create(
Thread_C_R Param,
Handle * const & H = 0,
const bool & CreateDetached = false,
const unsigned int & StackSize = 0,
const bool & CancelEnable = false,
const bool & CancelAsync = false
) const;
static int Join( Handle H );
static int Kill( Handle H );
static int Detach( Handle H );
};
As you might see, I stuck to the basics with this class. Some functions aren't implemented on Win32, but you can still pretend to use them since they are there, without worry of impact on code quality - that way you can know the specifics of how your thread will run on a non-Windows platform.
Like I said before, I don't use any virtual memory when creating the threads. Instead, I use a semaphore and a temporary object:
int Create( ... )
{
Semaphore S;
Temp_Object T( [ref to] S, [ref to] Parameter, this );
if ( Call_To_OS_Thread_Create(Static_Function,(void *)&T) Succeeds )
{
S.Wait();
return OK;
}
return FAILED;
}
static void Static_Function( void *T )
{
Thread *DestObj(((Temp_Object *)T)->ClassPtr);
Thread_T Copy_Of_Parameter(((Temp_Object *)T)->ParamRef);
((Temp_Object *)T)->Sem->Post();
}
As the pseudo-code shows, the create
function can safely pass a pointer to the object on its stack, since it will not exit until the thread function is done with it. The create
functions are also object-friendly because they only copy the Thread_T
once. If you're a veteran template writer, you may have noticed that the layout of the class causes problems when Thread_T
is a reference type. To get around this, just make a simple struct
with a reference as a value in it, and you'll be back in business. Just a quick note: hopefully you are acquainted with mutexes and semaphores as I suggested you should be, above. I have also included some very simple platform-specific classes names Semaphore
and Mutex
which have some basic functions that behave as expected. For Mutex
, these functions are: Lock
, Unlock
and Lock_Try
(for >= Win98). For Semaphore
, they are: Reset(value)
, Post
(i.e. Release
), Wait
, Wait_Try
and Value
. Note that the Mutex
class is not actually a mutex in Win32, but rather a critical section which is must faster than an inter-process mutex.
Usage
I hope these are enough to get you acquainted, along with the detailed description of the Thread
member functions above.
#include <iostream>
using namespace std;
#include <time.h>
#include "Thread.h"
#include "Mutex.h"
int ii = 0;
Mutex ii_lock;
Semaphore sem_done;
const static int rep = 1000;
class ThreadTester : private Thread<int>
{
public:
void Run()
{
for ( int i = 0; i < rep; ++i )
Create(i,0,true,2048);
sem_done.Wait();
}
private:
void ThreadMain(int &i)
{
ii_lock.Lock();
++ii;
if ( ii >= rep ) sem_done.Post();
ii_lock.Unlock();
}
};
int main()
{
clock_t c1, c2;
ThreadTester Test;
c1 = clock();
Test.Run();
c2 = clock();
cout << (double)rep/((double)(c2-c1)/(double)CLOCKS_PER_SEC) <<
" Threads per second!" << endl;
#ifdef WIN32
system("pause");
#endif
return 0;
}
#include <iostream>
using namespace std;
#include "Thread.h"
void MyThreadProc( int &I )
{
for ( int i = 0; i < I; ++i );
}
typedef Thread<int> Thread_Int;
int main()
{
Thread_Int::Handle H;
if ( Thread_Int::Create((Thread_Int::Handler)MyThreadProc,100000000,&H) )
{
cout << "Creating of thread failed!\n";
}
else
{
cout << "Waiting...\n";
Thread_Int::Join(H);
cout << "Thread is done.\n";
}
#ifdef WIN32
system("pause");
#endif
return 0;
}
I'm sure...
... That there's a lot more I could go in-depth about, with respect to threads, mutexes, semaphores and synchronization, but I chose to list this article as intermediate and hope the reader has read enough of the "Threads, Processes & IPC" section of CP to know what is going on. My aim was to show you a facility for easy portable multithreading with this particular class - I hope that I succeeded in my efforts and I look forward to your feedback.
Updates
- Updates made on 11/17/03:
- Another Win32 tweak - made the default create function
CreateThread
(after inspecting the _beginthread
code). You can still specify the other methods... see Win32/Thread.h for details. (BTW, it is a little faster for you speed freaks out there :))
- Updates made on 11/16/03:
- Removed
__stdcall
on the Thread<>::Handler
type - not necessary (Win32)
- Updates made on 11/12/03:
- Added functionality for Win32
Detach()
- closes the thread's handle. Unfortunately, a thread cannot detach itself as in Posix threads, so the best use is in creation or outside of the thread.
- Added the
Self()
function, which returns the thread's handle to itself. Only a pseudo-handle in Win32.
- Increased speed even more by using the same semaphore to synchronize the creation of all threads, along with a static Mutex.
- Explicitly specified the static thread init functions as
__stdcall
(Win32)
- Added a
StackSize
argument to the create
functions
- Used
_beginthreadex
/_endthreadex
instead of _beginthread
/_endthread
(Win32)