What are threads? Threads are like mini-programs that run inside you program. Sometimes you want to have multiple things happening at the same time. While it is true that you cannot literally have multiple things happening simaltaneously, you can get something very close to it. Windows decides when a thread will execute by assigning each thread a time slice. This means that each thread gets only a small amount of proccessor time before it must surrender the processor to another thread. So you get things virtually executing simaltaneously.
In the White Knight GUI Library this is achieved by utilizing the Thread class. The constructor for a Thread class takes a function of the following prototype:
This function is reffered to as the runnable function. When the thread is started this function is executed. Note following example, ThreadSample.cpp, is a console program.void XXX( void* param );
00006: /////////////////////////////////////////////////////////////////////////////// 00007: //Prototypes 00008: void fib( void* param ); 00009: 00010: /////////////////////////////////////////////////////////////////////////////// 00011: //Represents a parameter passed to Thread::start(). 00012: struct FIBPARAM 00013: { 00014: int ntimes; //The nuber of times to loop. 00015: long fib; //The ending Fibinocci number. 00016: char *prefix; //Label associated with this thread. 00017: }; 00018: 00019: /////////////////////////////////////////////////////////////////////////////// 00020: //Main funcion. 00021: void main() 00022: { 00023: FIBPARAM fp1 = { 30, 0, "Fib1 " }; //The parameter for fib1 00024: FIBPARAM fp2 = { 10, 0, "Fib2 " }; //The parameter for fib2 00025: 00026: Thread fib1( fib ); //Thread that runs the fib function 00027: Thread fib2( fib ); //Thread that runs the fib function 00028: ThreadGroup tg; //Holds a group of threads. 00029: 00030: tg.addThread( &fib1 ); //Add fib1 to the thread group 00031: tg.addThread( &fib2 ); //Add fib2 to the thread group 00032: 00033: fib1.start( &fp1 ); //Start fib1 00034: fib2.start( &fp2 ); //Start fib2 00035: 00036: tg.waitForCompletion(); //Wait for all threads to complete 00037: 00038: //Output the calculations. 00039: cout << "Fib1 calculated: " << fp1.fib 00040: << "\nFib2 calculated: " << fp2.fib << endl; 00041: 00042: return; 00043: }
In this example the fib() function will act as our runnable function. We pass it to both fib1's and fib2's constructor. Next we create a thread group. The purpose of a thread group is perform actions on a set of threads. In this case we need to wait for all the threads to finish executing before we move on from line 36. If we did not do this main() would finish executing, thereby exiting the program, before fib1 and fib2 even get a chance to execute. So in lines 30 and 31 we add fib1 and fib1 to the group so that we can wait on them. When we start fib1 we pass in fp1. This is the parameter received by the runnable function. So now the main thread is waiting on fib1 and fib2 to finish executing. So lets look at the fib() function.
00045: /////////////////////////////////////////////////////////////////////////////// 00046: //fib calculates fibanacci numbers. It expects a param to point to a FIBPARAM. 00047: void fib( void *param ) 00048: { 00049: //Declare the holder variables. 00050: long f1 = 1, f2 = 1, f; 00051: 00052: //Ouput that the thread is starting. 00053: cout << ((FIBPARAM*)param)->prefix << "Starting..." << endl; 00054: 00055: //Loop the number of times found in param. 00056: for ( int i = 0; i < ((FIBPARAM*)param)->ntimes; i++ ) 00057: { 00058: //Sleep a random period between 0 and 200 milliseconds 00059: Thread::sleep( rand() % 200 ); 00060: 00061: //Calculate the fibanacci number. 00062: f = f1 + f2; 00063: f1 = f2; 00064: f2 = f; 00065: } 00066: 00067: //Set the fib value of the param to the calculated number. 00068: ((FIBPARAM*)param)->fib = f; 00069: 00070: //Output that the thread is done. 00071: cout << ((FIBPARAM*)param)->prefix << "Done!!" << endl; 00072: }
This function uses the standard iterative algorithm for calculating Fibanacci numbers. There are three things worth mentioning in this function. First, param must be cast to get access to its members. Secondly, the fib member is used basically like a return value. Using param is the only way to return a value. Also if you pass a pointer pointing to the same memory to multiple threads, the value of param can be used to synchronize the threads. Thridly, on line 59 we use the sleep() method. The sleep() method takes a single parameter that specifies the minimum amount of time the thread is suposed to be suspended. When sleep is called it immediately surrenders the callers time slice, and enters the suspended state. When the specified number of milliseconds elapses, the thread will begin executing again when the the current thread's timeslice elapses. This means that sleep will not give you a precise waiting time, but it will give you something pretty close. If you execute the example, your output should state that fib2 completed long before fib1.
A note about threads and windows programs. You should never interupt the event thread. That is you should never call suspend(), stop(), waitForCompletion() from within a callback function. The reason is that you window will be unable to receive events and may become unstable.