Introduction To Basic Multithreaded Programming
by Viasil (20 December 1998)



Return to The Archives
Introduction


For starters, it's important to realize exactly what multithreaded programming is - and further, what it can do for you. If you don't need threads in your program, don't use them! If you don't know if you need threads, or if you just don't know what they are, you may find this tutorial helpful.

Here's the basic concept. The Operating System that you use was probably written with the idea of threads, or processes in mind. This means, you can run programs of your choosing. A process is a running program, and a program is a set of instructions that the machine can understand. Of course, you can run more than one process at a time. You probably are right now, correct? When you open up separate programs, they run as processes in their own separate memory space. This means that each program running has a chunk of memory to which it and only it can use. That's how many programs can run without changing variables and states of other running programs. Follow? If all of your system memory could be controlled by any arbitary running program, it would be chaotic!

But imagine for a second what it would be like to run a program in the same memory space as another running process. This process running inside of another processes' memory space is called a "Thread". So what's the point? Well, there are several good reasons for this architecture. First of all, it acts as a shared memory system. What if you want to write a program that shares all the global variables of another program you're working on. Of course there are many shared memory solutions, but I won't get into them. I'll propose a simple solution, which is Threads! By using threads, you are running separate programs all inside of one program!

So how does it work? Is it fast? Is there a lot of overhead involved? These are commom (yet great) questions for someone who has never written a multi-threaded program. Here are some answers:


Q: How does it work?


A: The Operating System has a scheduler for each thread (process) that is currently running. It divides up time slices for each of them which are executed in the order that the Operating System seems fit. It simply runs each one in some arbitrary order for a set number of milliseconds and then switches between them constantly.


Q: Is it fast?


A: Of course! One way to think about it is like this: The more processes that your program has running, the more time that your program can get from the system. But this isn't the best way to think about it. The switches from one thread to another (or from one process to another) happens so quickly that the entire system seems to be doing many different things at once! In reality, (unless you have multiple processors), every instruction is still exectuted sequentially. Luckily the Operating System developers implemented a system for you that is easy to use and can give the appearance of true parallelism!


Q: Is there a lot of overhead involved?


A: Not much. Which brings me to the sample code. I put together a trivial example so that you can actually test and write your own multithreaded code. Sound good? Great! Let's get going...

This is a simple application that creates 3 threads and runs them simultaneously. The work of the threads (i.e. what they actually do is explained inside the code).


 // First, always include <windows.h> for all the Win32 specific thread information

 #include <windows.h>
 #include <iostream.h> 

#define MAX_THREADS 3

// Prototypes are good and handy, but not necessary in this example. // These three functions are run by each of our three threads // Please note how the functions are declared: // In Win32, thread functions MUST be declared like this: // DWORD WINAPI <name>(LPVOID); // In short, // Return value *must* be DWORD WINAPI // And the parameter must be LPVOID DWORD WINAPI genericThreadFunc1(LPVOID); DWORD WINAPI printString(LPVOID); DWORD WINAPI printNumber(LPVOID);

// We need an array of Handles to threads HANDLE hThreads[MAX_THREADS];

// ...an array of thread id's DWORD id[MAX_THREADS];

// And a waiter (which I'll explain later) DWORD waiter;

// Here are the three functions that are defined. // They do trivial things and should be mostly self explanatory. DWORD WINAPI genericThreadFunc1(LPVOID n) { cout << "Thread started (genericThreadFunc1)..." << endl; for(int i = 0; i < 100; i++) { cout << "threadFunc1 says: " << i << endl; } cout << "...(genericThreadFunc1) Thread terminating." << endl; return (DWORD)n; }

DWORD WINAPI printString(LPVOID n) { cout << "Thread started (printString)..." << endl; // NOTE: In the next line, we make a pointer and cast what was passed in. // This is how you use the LPVOID parameters passed into the CreateThread call (below). char* str = (char*)n; for(int i = 0; i < 50; i++) { cout << "printString says: " << str << endl; } cout << "...(printString) Thread terminating." << endl; return (DWORD)n; }

DWORD WINAPI printNumber(LPVOID n) { cout << "Thread started (printNumber)..." << endl; int num = (int)n; for(int i = num; i < (num + 100); i++) { cout << "printNumber says: " << i << endl; } cout << "...(printHello) Thread terminating." << endl; return (DWORD)n; }

// Get ready, because here's where all the *REAL* magic happens int main(int argc, char* argv[]) { int CONSTANT = 2000; char myString[20]; strcpy(myString,"Threads are Easy!");

// Here is where we call the CreateThread Win32 API Function that actually creates and // Begins execution of a thread. Please read your help files for what each parameter // Does on your Operating system. // Here's some basics: // Parameter 0: Lookup // Parameter 1: Stack size (0 is default which means 1MB) // Parameter 2: The function to run with this thread // Parameter 3: Any parameter that you want to pass to the thread function // Parameter 4: Lookup // Parameter 5: Once thread is created, an id is put in this variable passed in hThreads[0] = CreateThread(NULL,0,genericThreadFunc1,(LPVOID)0,NULL,&id[0]); hThreads[1] = CreateThread(NULL,0,printString,(LPVOID)myString,NULL,&id[1]); hThreads[2] = CreateThread(NULL,0,printNumber,(LPVOID)CONSTANT,NULL,&id[2]);

// Now that all three threads are created and running, we need to stop the primary thread // (which is this program itself - Remember that once "main" returns, our program exits) // So that our threads have time to finish. To do this, we do what is called "Blocking". // We're going to make main just stop and wait until all three threads are done. // This is done easily with the next line of code. Please read the help file about // the specific API call "WaitForMultipleObjects". waiter = WaitForMultipleObjects(MAX_THREADS,hThreads,TRUE,INFINITE);

// After all three threads have finished their task, "main" resumes and we're now ready // to close the handles of the threads. This is just a bit of clean up work. // Use the CloseHandle (API) function to do this. (Look it up in the help files as well) for(int i = 0; i < MAX_THREADS; i++) { CloseHandle(hThreads[i]); }

return 0; }


Closing


Thus far, there should be plenty of code for you to play with and rewrite. Unfortunately, there is *MUCH* more to multithreaded programming than what I have shown here. If there is sufficient interest in this kind of tutorial (which of course would become more and more advanced with time), please e-mail the webmaster of this site at flipcode@flipcode.com If there is enough interest, I will continue to write about the many more important issues of multithreaded programming (such as thread synchronization, management and intercommunication). But until then, happy hacking!


Downloads


Note: The sample application is win32 console program.

  • Download the Sample Application (18k)
  • Download the Sample Application + Source Code (20k)

  •  

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.