There are several standard, cross platform ways to create high performance, multithreaded programs. There are no standard ways to spawn threads with the C++ language, which means that sometimes we have to resort to using compiler-specific methods to create threads for our programs. This tutorial will be focused on how to easily create work threads for your windows, or WIN32, program using Microsoft Visual Studio.
There are generally two main purposes to having multithreaded programs for Windows. One common use for this is so that one thread can manage the GUI, and another thread performs computationally intensive work. This way, the user is still able to use a program’s GUI, even if the program is busy performing a task. The other application of work threads is for the explicit purpose of improving program performance by utilizing multiple processor cores. The technique discussed in this tutorial can be applied to either of these scenarios.
Work thread function
Below is the function which we will want to be executed on a work thread.
#define C_DATA_SIZE 30000000 unsigned int __stdcall computeData( void *arg ) { printf( "Computing Data...\n"); double *pData = (double*)arg; for (int i=0; i < C_DATA_SIZE; i++) { pData[i] = sqrt((double)i) * sin((double)i) / (cos((double)i)+.05f)*tan((double)i) / 12.345; } return 0; }
The most important thing for you to note is the function declaration. It returns an unsigned int, and uses a __stdcall specifier. This will be needed later for your program to actually compile. As you can see, the argument list comes in the form of a void pointer. Since anything can be cast into a void pointer, you can pass whatever information you would like to your function. In this particular tutorial, we will be passing the address of an array of doubles into this function.
Spawning the work thread
The function we will use to spawn the work thread is _beginthreadex. This function is powerful and versatile. There is another function called _beginthread, however it is much less desireable to use this function for reasons I won’t delve into. If you stick to using _beginthreadex, you’ll be safe. Instead of giving you the function prototype, it is much easier to see an example of how it’s called.
HANDLE handleT1; unsigned int myThreadId; handleT1 = (HANDLE)_beginthreadex( NULL, // security 0, // Stack size, 0 is usually fine. computeData, // This is the function that will be executed (void*)myData, // Argument list. In this case, a pointer to our data array 0, // 0 will start thread immediately. See MSDN for more info &myThreadId ); // Thread ID. This can be NULL, if you'd like.
As you can see, there are six arguments, but four of the arguments can be filled with 0 or NULL, which makes this function painless to use. Notice how the actual name of the function is used as an argument. This is because we are actually passing a function pointer. Remember that whatever function you are using must exactly match the type of function pointer _beginthreadex is expecting. You can see an example of a properly declared work-thread function near the beginning of this tutorial.
Note: You may need to include windows.h and process.h in order to compile with this function.
Synchronization
In almost all multithreaded environments, it is important to synchronize threads. Often, you will find that the main thread will need to wait until all worker threads are finished before continuing with a given task. In order to accomplish this, we can use one more function.
printf("Waiting for workthread to complete...\n"); WaitForSingleObject( handleT1, INFINITE ); CloseHandle(handleT1); // After you're done with a thread forever, close the handle.
The code in this snippet is fairly straightforward. The main function will wait for a single object to finish. After we are done with the thread, we need to close the associated handle.
Memory organization
It’s important to mention that all threads created in this manner will have the same address space as the main program. For example, if the main program allocates an array with malloc or new, that array is accessable to any work thread. Of course, a work thread will have it’s own independent stack, but it will share pretty much all memory for the variables contained in your main program.’
Complete code listing
Below is a complete listing of the code which has been tested with Visual Studio 2008 Express.
#include "stdafx.h" #include <windows.h> #include <process.h> #include <math.h> #define C_DATA_SIZE 30000000 unsigned int __stdcall computeData( void *arg ) { printf( "Computing Data...\n"); double *pData = (double*)arg; for (int i=0; i < C_DATA_SIZE; i++) { pData[i] = sqrt((double)i) * sin((double)i) / (cos((double)i)+.05f)*tan((double)i) / 12.345; } return 0; } int main(int argc, char* argv[]) { double *myData = (double*)malloc(sizeof(double)* C_DATA_SIZE); HANDLE handleT1; unsigned int myThreadId; handleT1 = (HANDLE)_beginthreadex( NULL, // security 0, // Stack size, 0 is usually fine. computeData, // This is the function that will be executed (void*)myData, // Argument list. In this case, a pointer to our data array 0, // 0 will start thread immediately. See MSDN for more info &myThreadId ); // Thread ID. This can be NULL, if you'd like. printf("Waiting for workthread to complete...\n"); WaitForSingleObject( handleT1, INFINITE ); CloseHandle(handleT1); // After you're done with a thread forever, close the handle. printf("Finished\n"); free(myData); // Free up the memory we allocated. return 0; }