First let's see how the comp.programming.threads FAQ answers the question "What is a thread"?
"A thread is an encapsulation of the flow of control in a program. Most people are used to writing single-threaded programs - that is, programs that only execute one path through their code "at a time". Multithreaded programs may have several threads running through different code paths "simultaneously".
Every program that you wrote in 142, 143, and 373 had a single thread.
This thread began at the beginning of main
and continued to execute
until your main function returned. This is known as a single-threaded
application.
A multi-threaded application is a lot like having multiple functions
in your program that all execute at the same time. (Because the CPU
can only run one thread at a time, the threads don't actually execute
simultaneously, but instead the CPU provides this illusion by
switching between the threads very quickly.) In Windows, a program
explicitly creates another thread by calling CreateThread
. The
most important argument to CreateThread
is the function to begin executing.
CreateThread( Foo )
is different than calling Foo()
. A call to
Foo()
will not return until Foo
returns. CreateThread( Foo )
creates
another thread that begins executing at the start of the Foo
function.
The CreateThread
call will return before Foo
finishes executing, and
two threads will be active: the main
thread and the Foo
thread. The
Foo
thread continues to be an active thread until it returns.
Here is a simple program that creates two additional threads. One
thread prints "Foo" and the other one prints "Bar". The main
thread
starts the Foo
thread and Bar
thread and then sleeps for 5 seconds
(i.e. 5000 ms). When the main
thread exits, the entire process exits
even though some of the other threads are still active. You can
try out an executable version of this program here.
#include <windows.h> #include <stdio.h> unsigned long __stdcall Foo( void * x ) { while( 1 ) { printf( "Foo\n" ); } return 0; } unsigned long __stdcall Bar( void * x ) { while( 1 ) { printf( " Bar\n" ); } return 0; } void main() { HANDLE threadFoo, threadBar; DWORD idFoo, idBar; threadFoo = CreateThread( NULL, 0, Foo, NULL, 0, &idFoo ); threadBar = CreateThread( NULL, 0, Bar, NULL, 0, &idBar ); Sleep( 5000 ); }
The output of this program is:
Foo Foo ... Foo Bar Bar ... Bar Foo Foo ... Foo Bar Bar ... Bar . . .
From the output, it's very apparent that the CPU is switching back and
forth between the two threads. Another very important fact about
threads is that they share the same address space. This means that
when one thread changes the value of a variable, the other threads
will see that change. We've changed the above program to have the
user control when the threads stop. The two threads continue to print
"Foo" or "Bar" until a global variable, stopPrinting
, is set to TRUE
.
The main
thread sets stopPrinting
to TRUE
after the user hits Enter,
and at this point, the Foo
thread and the Bar
thread exit. The
main
thread uses the call WaitForSingleObject
(described below) to wait for
these two threads to exit. You can try out an executable version of
this program here.
The output of the above program is:#include <windows.h> #include <stdio.h> BOOL stopPrinting = FALSE; unsigned long __stdcall Foo( void * x ) { while( !stopPrinting ) { printf( "Foo\n" ); } printf( "exiting from Foo\n" ); return 0; } unsigned long __stdcall Bar( void * x ) { while( !stopPrinting ) { printf( " Bar\n" ); } printf( "exiting from Bar\n" ); return 0; } int main() { HANDLE threadFoo, threadBar; DWORD idFoo, idBar; char c; printf( "I'm about to start some threads. Hit Enter to stop.\n" ); Sleep( 5000 ); threadFoo = CreateThread( NULL, 0, Foo, NULL, 0, &idFoo ); threadBar = CreateThread( NULL, 0, Bar, NULL, 0, &idBar ); scanf( "%c", &c); // wait for the user to hit Enter stopPrinting = TRUE; // // The WaitForSingleObject call waits for something // to happen. The calling thread essentially goes to sleep // until what it is waiting for happens. // // The first parameter is what we want to wait for. In this // case, we want to wait for the threadFoo (and threadBar) // thread to finish. // // The second parameter denotes how long we want to wait. // INFINITE means we want to wait forever. // WaitForSingleObject( threadFoo, INFINITE ); WaitForSingleObject( threadBar, INFINITE ); printf( "The threads have finished. Press Enter to quit.\n" ); scanf( "%c", &c); // wait for the user to hit Enter return 0; }
I'm about to start some threads. Hit Enter to stop. Foo Foo ... Foo Bar Bar ... Bar Foo Foo ... Foo Bar Bar ... Bar <User hits Enter> exiting from Foo exiting from Bar The threads have finished. Press Enter to quit.
Increasing the performance of a program is sometimes a reason to use threads. If the program is running on a computer with multiple CPUs, then each thread can run on a separate CPU, whereas a single-threaded program could only run on a single CPU. Or if a program is both I/O and CPU intensive, then making it multithreaded will allow it to do I/O at the same time as using the CPU, whereas a single-threaded program couldn't use the CPU while it is waiting for I/O.
A more common reason to use threads is because it makes it easier to break a program into smaller tasks that need to happen at the same time.
Take Microsoft Word for example. It does a lot of stuff all at the same time. A sample of things that are happening concurrentlyYou could do this with only one thread, but it would be ugly, it would be difficult to keep Word interactive, and it would harder to code. Here's an example of what a single-threaded solution would look like:
word_main() { while( !exit ) { if the user has typed something, then show it on the screen if the last word was typed was a typo, then correct it if the last word was misspelled, then underline it if it looks like the user is making a list, then indent it if a document is being printing, then send the next few lines to the printer if auto-saving a document, then save the next part of the document make the paperclip blink ... } }
A much cleaner solution is to have each of these tasks handled by a separate thread.
word_main() { CreateThread( DisplayUserInput ); CreateThread( CorrectTypos ); CreateThread( CheckSpelling ); CreateThread( AutoFormat ); CreateThread( AnimatePaperClip ); ... }
Each of these threads runs sequentially without worrying about what the other threads were doing. (The PrintDocument and AutoSave threads would be created only when the user printed a document or when Word decided to save the document). In DOS and Windows 3.1, you can't print a document and edit the document at the same time because they are single-threaded operating systems, which is a big drag.
Not all Microsoft applications are multithreaded. Outlook, which is Microsoft's email client, is single-threaded. This means that you can't download new email messages and compose a new message at the same time, which is a drag. I was told that Microsoft chose to make Outlook single-threaded because it would have been too expensive (by some metric, e.g. $$$ or time-to-market) to make it multi-threaded.
If the concept of threads is still unclear, then please ask me questions on Wednesday.