Here we present a very simple multi-threaded program I call Thread Racer.
In the subversion repository you will find it called ThreadTestSimple
When we create a thread using the Platinum::Thread library, we use a function called
Platinum::Thread::Create
This function takes a pointer to a void function which accepts a void pointer as it's argument.
Which of course looks like this
void MyFunc(void* mydata);
So calling Platinum::Thread::Create looks like this
ThreadID myThread = Platinum::Thread::Create(MyFunc,(void*)mydata);
Calling it this way means that MyFunc is going to be executed as soon as the thread is created, and since we are using void* we can use reinterpret_cast to recast the void* to anything we want.
A better solution would be to use templates for the exact same functionality, except using a template would cause a lot of bulk to occur in the compiled code since each distinct object type that would use the thread create function would generate a new thread create function that accepts those parameters. In the future we may go with templates anyways just for clarity sake, but for now using void pointers is perfectly valid as long as we make sure to change it back into the exact same type it originated from.
Here is the function we will be calling when we do the thread creation, it's called print and it's job is to print a character to standard out multiple times and yield periodically to other threads. Also it uses a mutex lock which will prevent multiple threads from trying to write at the same time.
void Print( void* data ) {
char* c = reinterpret_cast(data);
Platinum::Thread::Yield();
for( int i = 0; i < 200; i++ ) {
mutex.Lock();
for( int j = 0; j < 5; j++ ) {
std::cout << c;
std::cout.flush();
}
mutex.Unlock();
Platinum::Thread::Yield();
}
}
As you can see the function is pretty standard fare, with the exception of what I have mentioned above.
Notice it is referencing an object called mutex. mutex means "Mutually Exclusive", or Only one thread at a time here please!
For this example on a single core processor it's probably overkill, but if you were on a multicore system, without this, one thread would execute in parallel for each core, possibly causing our output to be munged.
At the very least it will help to keep our results consistent, but don't take my word for it, feel free to comment those lines out and execute it on a multi-core system a few times, you will see that the results will not be consistent.
By the way the mutex object is defined outside the main function bodies, it's declaration is simply.
Platinum::Thread::Mutex mutex;
Now for the main portion of our program.
All we plan to do is create 4 threads each one will call our Print function with a parameter of 1,2,3 or 4 depending on the order in which the thread is created. We will then force out of order execution of the threads by telling the system (suggesting really), to wait for thread 4 to finish before having the others finish.
Now the output will largely depend on your systems scheduling, and I don't get exactly the same results between Linux, Vista and Mac, which is why I call this program Thread Racer. Each OS will schedule each thread slightly differently.
Here is our main
int main() {
using namespace Platinum;
Thread::ThreadID tFirst = Thread::Create( Print,(void*) "1" );
Thread::ThreadID tSecond = Thread::Create( Print,(void*) "2" );
Thread::ThreadID tThird = Thread::Create( Print,(void*) "3" );
Thread::ThreadID tFourth = Thread::Create( Print,(void*) "4" );
Thread::WaitForFinish( tFourth );
Thread::WaitForFinish( tThird );
Thread::WaitForFinish( tSecond );
Thread::WaitForFinish( tFirst );
return 0;
}
Hypothetically 4 should finish first, but like I said, thats really up to your system.
What this does show though is the importance of protecting certain sections of code.
Just for fun comment out the mutex lock and unlock.
Instead of
11111222223333344444111112222233333444444444433333222221111144444333332222211111444443333322222111114444433333222221111144444333332222211111444443333322222
It will look more like
11111222223333344444111112222244444333331111122442224443333311111333334444422222111113333344444222221111133333444442222211111333334444422222111113333344444222
See where the data got improperly injected? It's hard to tell and that's the whole point of this program.
You need to make sure to protect any place in code where you are copying data from your thread into a shared space such as std::out or tcp::send or any other place where we have a shared space.
Mutex locks are so cheap that they are almost free, so always remember When In Doubt Lock It Out!
Our next section will show how we can use threads in a more practical manner, by fixing the TCPChat application to utilize threads.