| . | . |
| . | ![]() |
. | . |
| . | . |
| . |
Multithreaded Programming - Part 1.How can I get my program to do two things at the same time? This is a classic question that arises so often. You may, for example, have a lengthy calculation or database operation to do, but while that is running, your user interface is unresponsive. There are limitless scenarios out there which would be easy if only your program could do two or more things at the same time - so can it? Happily yes, it can. It requires a bit of thought to get it right, but essentially, it is not overly difficult. This is a very introductory tutorial. To cover all the in's and out's of multithreaded design, coding and linking would require many hundreds of pages. The material presented here is enough to get you going however. I have a more advanced tutorial that I will add as and when I get time, but a lot of people had asked me for something basic now, so that is what I have done. At least a couple of the key concepts should be clear, or at least not completely obscure, by the time you've worked through these pages. I'm going to inject a little disclaimer here! If you have only a single processor in your computer, it is only capable of processing one thing at a time, although some modern processors are beginning to look like multiprocessors. When in this tutorial I talk about doing two or more things at the same time, what I mean is it appears to be doing so! I will be assuming you have a single processor system. Right, let's begin. Have a look at this program.
#include <windows.h>
#include <iostream>
using namespace std;
void Func1(void);
void Func2(void);
int main()
{
Func1();
Func2();
cout << "Main exit" << endl;
return 0;
}
void Func1(void)
{
int Count;
for (Count = 1; Count < 11; Count++)
{
cout << "Func1 loop " << Count << endl;
}
return;
}
void Func2(void)
{
int Count;
for (Count = 10; Count > 0; Count--)
{
cout << "Func2 loop " << Count << endl;
}
return;
}
Think about what happens when you run it. Windows creates a process, then calls the main() function. Main calls Func1() and then Func2(), after which the main() returns and Windows closes the process. You have, in actual fact, run a thread. A thread is simply a chain of events - nothing more. All programs have at least one thread. The program is quite silly in itself, but it demonstrates a classic problem. Func1() and Func2() are largely independent of each other, (not completely - we'll come back to that!), and yet, Func2() cannot begin running until Func1() has finished. Func1() simply counts up from 1 to 10, and Func2() counts down from 10 back to 1. The output from the program looks like this. What I'll do now is modify the program so that Func1() and Func2() run in their own threads. I said at the start it is easy to do. If you follow the tutorial further, you'll see that just being easy, does not make it right!!!
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
void Func1(void *);
void Func2(void *);
int main()
{
_beginthread(Func1,
0,
NULL);
_beginthread(Func2,
0,
NULL);
Sleep(10000);
cout << "Main exit" << endl;
return 0;
}
void Func1(void *P)
{
int Count;
for (Count = 1; Count < 11; Count++)
{
cout << "Func1 loop " << Count << endl;
}
return;
}
void Func2(void *P)
{
int Count;
for (Count = 10; Count > 0; Count--)
{
cout << "Func2 loop " << Count << endl;
}
return;
}
The first thing to notice, is that I have included an additional header file, <process.h> which contains the prototypes of the new functions I will use in this program. Notice also, I have changed both the prototypes and first line of Func1() and Func2(). In particular, instead of taking no parameters, (a void parameter list), they now take a single parameter of type void *, a void pointer. This is required, even though we will not use it in this example. In the main() function, I have replaced the function calls to Func1() and Func2() by calls to _beginthread(). This is one of several API functions that start a seperate thread running. It is one of the simplest, but also offers the least amount of control, and has a few issues which we will discuss later. The first parameter to the API is the name of the function to run, without the parentheses. The other two parameters are set to default values. The final code change to note is that I've added a call to Sleep() to the main() function, after the threads have been envoked, and before the exit message. Again, I'll talk about that later. If you are not familiar with Sleep(), this function suspends activity for the specified number of milliseconds, in this case, 10,000ms, (10 seconds). These code changes are fairly straightforward to make. There are potentially other changes that you will need to make though, not to the code, but to your environment. Visual C++ 6.0 by default, does not link to the multithreaded libraries, thus, the above will compile, but the linker will fail because it cannot find the implementation of _beginthread(). In VC++ 6, to link with the multi threaded libraries, from the top menu, select the "Project" option, and then select "Settings", this should open a dialog box. In the right hand part of the dialog, click the "C/C++" tab. Open the drop down box labelled "Category", and highlight "Code Generation". Now expand the drop down box labelled "Use run-time library", highlight "Debug Multithreaded" and click "OK" to close the dialog. Visual Studio.NET 2003 seems to default to the multithreaded libraries, mine did anyway. If yours does not, you can change the setting by selecting "Project" from the top menu, then "Properties". In the tree view control on the left open "Configuration Properties", and beneath that "C/C++" and highlight "Code Generation". Now in the right hand pane, look for "Runtime Library", and if the choice to it's right is not "Multithreaded" or "Multithreaded Debug", click on "Runtime Library" to enable the drop down box to it's right and highlight the desired setting. Finally, click "OK" to save and exit the dialog. If you are usng a different compiler, and the above code does not compile and link, you'll need to refer to your documentation or some other help resource. If you cannot compile, link and run the above, you will not be able to run anything else in these tutorials! This is one of the reasons I started with a really poor, but very simple piece of code - you need to be able to do this. You must crawl before you walk before you fly! Assuming you've got that to work, try running it a few times. Although it depends to a certain extent on your machine and what else is running on it, you'll probably find that the output looks different every time! Here is a typical output from my machine. Oh dear! Actually, if you look carefully, it is all there, it is just hopelessly mixed up. Do you remember above I said the functions were "largely independent"? They in fact share an important resource - the screen! Whenever a multithreaded program shares resources of any kind between it's threads, precautions must be taken to avoid a variety of potential troubles. With single threaded programs, the fact that the operating system slices processor time between applications is not important. The application resumes at the point it was interrupted, (usually called pre-empted incidently). It can be pre-empted at any point, but this has no side effects. With a multithreaded program, the threads are pre-empted in just the same way, they can be interupted at any point, any instruction, and I mean machine code instruction, not a high level language instruction! Remember, the compiler converts a single line of a high level language program into several, sometime many machine code statements. If you look at the sample output from the program above, the thread running Func1() began and it wrote "Func1 loop " but before it output the "1" and the newline character, it was pre-empted. The thread running Func2() then started and output the entire first line of it's output. Func1() then started again by completing it's first line, the 1 and a newline, then managed 2 full lines more before it was pre-empted again. If you follow the output carefully, you can see where each thread was pre-empted. In a simple program like this, it can be seen what has happened, but in a more complicated program with dozens of threads, the result can be a total gibberish of nonsensical characters. What we need is some way of synchronising the activity of the threads to prevent this, and many other types of trouble from biting us. We will begin to discuss synchronisation in the next part of the tutorial. |
. |
| . | . |
| . | . |
| . | . |