Introduction
This article is meant for beginners who want to take initial lessons in creating threads. This article explains how to create threads using the CreateThread()
function. When I started to learn multithreaded programming, I had a very hard time finding simple and descriptive programs and articles that would demonstrate and explain concepts related to multithreading in simple plain English. And this is what motivated me to write my first article on multithreading, for CodeProject.
Background
I am presenting a program which will demonstrate the creation and concurrent execution of three threads using the Windows API CreateThread()
.
Using the code
I have provided the source code in a zip file source.zip. I have provided the executable "Test1.exe" in Test1.zip. When you run Test1.exe from the Windows command prompt, you will see the output in a DOS box as shown in the picture above.
The code
#include <windows.h> <WINDOWS.H>
#include <strsafe.h> <STRSAFE.H>
#include <stdio.h><STDIO.H>
#define BUF_SIZE 255
void DisplayMessage (HANDLE hScreen,
char *ThreadName, int Data, int Count)
{
TCHAR msgBuf;
size_t cchStringSize;
DWORD dwChars;
StringCchPrintf(msgBuf, BUF_SIZE,
TEXT("Executing iteration %02d of %s"
" having data = %02d \n"),
Count, ThreadName, Data);
StringCchLength(msgBuf, BUF_SIZE, &cchStringSize);
WriteConsole(hScreen, msgBuf, cchStringSize,
&dwChars, NULL);
Sleep(1000);
}
DWORD WINAPI Thread_no_1( LPVOID lpParam )
{
int Data = 0;
int count = 0;
HANDLE hStdout = NULL;
if( (hStdout =
GetStdHandle(STD_OUTPUT_HANDLE))
== INVALID_HANDLE_VALUE )
return 1;
Data = *((int*)lpParam);
for (count = 0; count <= 4; count++ )
{
DisplayMessage (hStdout, "Thread_no_1", Data, count);
}
return 0;
}
DWORD WINAPI Thread_no_2( LPVOID lpParam )
{
int Data = 0;
int count = 0;
HANDLE hStdout = NULL;
if( (hStdout =
GetStdHandle(STD_OUTPUT_HANDLE)) ==
INVALID_HANDLE_VALUE )
return 1;
Data = *((int*)lpParam);
for (count = 0; count <= 7; count++ )
{
DisplayMessage (hStdout, "Thread_no_2", Data, count);
}
return 0;
}
DWORD WINAPI Thread_no_3( LPVOID lpParam )
{
int Data = 0;
int count = 0;
HANDLE hStdout = NULL;
if( (hStdout =
GetStdHandle(STD_OUTPUT_HANDLE))
== INVALID_HANDLE_VALUE )
return 1;
Data = *((int*)lpParam);
for (count = 0; count <= 10; count++ )
{
DisplayMessage (hStdout, "Thread_no_3", Data, count);
}
return 0;
}
void main()
{
int Data_Of_Thread_1 = 1;
int Data_Of_Thread_2 = 2;
int Data_Of_Thread_3 = 3;
HANDLE Handle_Of_Thread_1 = 0;
HANDLE Handle_Of_Thread_2 = 0;
HANDLE Handle_Of_Thread_3 = 0;
HANDLE Array_Of_Thread_Handles[3];
Handle_Of_Thread_1 = CreateThread( NULL, 0,
Thread_no_1, &Data_Of_Thread_1, 0, NULL);
if ( Handle_Of_Thread_1 == NULL)
ExitProcess(Data_Of_Thread_1);
Handle_Of_Thread_2 = CreateThread( NULL, 0,
Thread_no_2, &Data_Of_Thread_2, 0, NULL);
if ( Handle_Of_Thread_2 == NULL)
ExitProcess(Data_Of_Thread_2);
Handle_Of_Thread_3 = CreateThread( NULL, 0,
Thread_no_3, &Data_Of_Thread_3, 0, NULL);
if ( Handle_Of_Thread_3 == NULL)
ExitProcess(Data_Of_Thread_3);
Array_Of_Thread_Handles[0] = Handle_Of_Thread_1;
Array_Of_Thread_Handles[1] = Handle_Of_Thread_2;
Array_Of_Thread_Handles[2] = Handle_Of_Thread_3;
WaitForMultipleObjects( 3,
Array_Of_Thread_Handles, TRUE, INFINITE);
printf("Since All threads executed"
" lets close their handles \n");
CloseHandle(Handle_Of_Thread_1);
CloseHandle(Handle_Of_Thread_2);
CloseHandle(Handle_Of_Thread_3);
}
Explanation of the code
Our goal is to create and concurrently execute three threads using Windows API CreateThread()
. Let the three threads be Thread_no_1
, Thread_no_2
, and Thread_no_3
. Each thread is represented by a function. So let's name the functions. The function corresponding Thread_no_1
is named as Thread_no_1()
. The function corresponding to Thread_no_2
is named as Thread_no_2()
. The function corresponding Thread_no_3
is named as Thread_no_3()
. Thus, we have three functions, each representing one specific thread. Thus, when we say three threads running concurrently, we mean three functions are executing concurrently. In other words, when we say three threads Thread_no_1
, Thread_no_2
, and Thread_no_3
are running concurrently, we mean three functions Thread_no_1()
, Thread_no_2()
, and Thread_no_3()
are being executed concurrently. Therefore, we have defined three functions in our program. The three functions are:
Thread_no_1()
Thread_no_2()
Thread_no_3()
Each thread works on a piece of data fed to it or on globally available data. In our case, we are not using any global data. So threads in our example will use the data fed to them. And the thread in our program is represented by a function. So feeding data to a thread means feeding data to a function. How do we feed data to a function? By passing arguments to a function. Thus, our functions that represent the threads accept the data through arguments. So now, every function that represents the thread looks like this...
Thread_no_1(LPVOID lpParam)
Thread_no_2(LPVOID lpParam)
Thread_no_3(LPVOID lpParam)
where "LPVOID lpParam
" is a long pointer to a void
.
Who must be passing data to these threads, i.e., to the thread functions? Well....it's done by the Windows API CreateThread()
. How this is done will be discussed shortly. Wonder who calls the CreateThread()
API? It's the main()
program that calls the CreateThread()
API to simply create a thread. And where does the program execution begin from? It begins from main()
. Thus, main()
calls CreateThread()
. The function CreateThread()
creates threads. Threads execute concurrently and terminate. That's the story!
Let us now understand the implementation of the threads, i.e., the thread functions. Consider the first thread function, Thread_no_1
.
Thread_no_1():
The prototype of the function is "DWORD WINAPI Thread_no_1( LPVOID lpParam )
". No questions on this please! It has to be like this. Let's accept it at a beginner's level. The function accepts the data fed to it in the form of a long void pointer variable lpParam
. It defines two integer variables, Data
and count
. It defines a variable hStdout
of data type HANDLE
. Next, the function gets a handle to the screen (standard output) using the function "GetStdHandle()
". The thread function returns if it fails to obtain a handle to the screen. Next, the function extracts the data from lpParam
and stores it in the variable Data
. This data was passed to Thread_no_1()
through the function CreateThread()
called by main()
. Next, the function implements a for
loop that runs four times. The function DisplayMessage()
is called from within the for
loop. So the function DisplayMessage
executes four times. Its job is to display a string which indicates the thread under execution, the iteration number, and the data that was passed to the thread.
Thread_no_2():
The prototype of the function is "DWORD WINAPI Thread_no_2( LPVOID lpParam )
". No questions on this please! It has to be like this. Let's accept it at a beginner's level. The function accepts the data fed to it in the form of a long void pointer variable lpParam
. It defines two integer variables, Data
and count
. It defines a variable hStdout
of data type HANDLE
. Next, the function gets a handle to the screen (standard output) using the function "GetStdHandle()
". The thread function returns if it fails to obtain a handle to the screen. Next, the function extracts the data from lpParam
and stores it in the variable Data
. This data was passed to Thread_no_2()
through the function CreateThread()
called by main()
. Next, the function implements a for
loop that runs seven times. The function DisplayMessage()
is called from within the for
loop. So the function DisplayMessage
executes seven times. Its job is to display a string which indicates the thread under execution, the iteration number, and the data that was passed to the thread.
Thread_no_3():
The prototype of the function is "DWORD WINAPI Thread_no_3( LPVOID lpParam )
". No questions on this please! It has to be like this. Let's accept it at a beginner's level. The function accepts the data fed to it in the form of a long void pointer variable lpParam
. It defines two integer variables, Data
and count
. It defines a variable hStdout
of data type HANDLE
. Next, the function gets a handle to the screen (standard output) using the function "GetStdHandle()
". The thread function returns if it fails to obtain a handle to the screen. Next, the function extracts the data from lpParam
and stores it in the variable Data
. This data was passed to Thread_no_3()
through the function CreateThread()
called by main()
. Next, the function implements a for
loop that runs 10 times. The function DisplayMessage()
is called from within the for
loop. So the function DisplayMessage
executes ten times. Its job is to display a string which indicates the thread under execution, the iteration number, and the data that was passed to the thread.
Now, let's consider the function DisplayMessage()
.
DisplayMessage():
This function returns nothing. This function accepts four parameters. The first parameter is a handle to the screen, the second parameter is the name of the thread, the third parameter is the data of the thread, and the last parameter is the iteration number the thread is executing. This function declares a buffer msgbuff
to hold the message to display on the screen. The function declares cchStringSize
to hold the size of the message to be displayed. The function declares dwChars
as a DWORD
. It is required by the WriteConsole
function. The function StringCchPrintf()
copies the message to be displayed in msgbuff
. The function StringCchLength()
calculates the length of the message to be displayed. The function WriteConsole()
displays the message. After displaying the message, it sleeps for a second.
To reiterate, the job of every thread in our program is to display a message after every second.
Over to the main()
program.
Main Program:
The goal of the main program is to create three threads and let them run concurrently till they terminate. A thread is created using the CreateThread()
function. When the CreateThread()
function creates a thread, it returns a thread handle. Our main program has to store this handle. Since we are creating three threads, our program needs to store three thread handles, so our program defines three handle variables viz. Handle_Of_Thread_1
, Handle_Of_Thread_2
, and Handle_Of_Thread_3
. Our program also defines three data variables which will contain the data to be passed to the threads. Thus, the contents of the variable Data_Of_Thread_1
will be passed to first thread, i.e., Thread_no_1()
. The contents of the variable Data_Of_Thread_2
will be passed to the second thread, i.e., Thread_no_2()
, and the contents of the variable Data_Of_Thread_3
will be passed to the third thread, i.e., Thread_no_3
. Next, we declare an array to hold the three thread handles. The name of the array is Array_Of_Thread_Handles[3]
. The purpose of this array will be discussed shortly.
Next, an attempt is made to create the first thread by making a call to the function CreateThread()
. CreateThread
accepts six parameters. Our goal is to create a simple thread. So, we will focus on the third and fourth parameters of the CreateThread()
function. The address of the function Thread_no_1()
is passed as the third parameter. The address of the variable Data_Of_Thread_1
is passed as the fourth parameter. This is the way we pass data to the thread. The CreateThread()
function creates a thread and the thread starts executing. The function CreateThread()
returns Thread_no_1
's handle. This handle is collected in the handle variable Handle_Of_Thread_1
. If a NULL
value is returned, the program exits with the exit value of Data_Of_Thread_1
.
Next, an attempt is made to create the second thread, by making a call to the function CreateThread()
. Our goal is to create a simple thread. The address of the function Thread_no_2()
is passed as the third parameter. The address of the variable Data_Of_Thread_2
is passed as the fourth parameter. This is the way we pass data to the thread. The CreateThread()
function creates a thread and the thread starts executing. The function CreateThread()
returns Thread_no_2
's handle. This handle is collected in the handle variable Handle_Of_Thread_2
. If a NULL
value is returned, the program exits with the exit value of Data_Of_Thread_2
.
Next, an attempt is made to create the third thread by making a call to the function CreateThread()
. The address of the function Thread_no_3()
is passed as the third parameter. The address of the variable Data_Of_Thread_3
is passed as the fourth parameter. This is the way we pass data to the thread. The CreateThread()
function creates a thread and the thread starts executing. The function CreateThread()
returns Thread_no_3
's handle. This handle is collected in the handle variable Handle_Of_Thread_3
. If a NULL
value is returned, the program exits with the exit value of Data_Of_Thread_3
.
At this point, all the three threads are executing concurrently, and you get to see the output on the screen as shown in the picture above. Thread_no_1
has a for
loop that loops four times. Thread_no_2
has a for
loop that loops seven times. Thread_no_3
has a for
loop that loops ten times. So, Thread_no_1
will complete first, Thread_no_2
will complete next, and Thread_no_3
will be the last to complete.
WaitForMultipleObjects()
Now that all the three threads are executing concurrently, you get to see the output on the screen as shown in the picture above. We wait to let all the threads execute, and then and then only, should we exit from our program. In order to do so, we need to call the function WaitForMultipleObjects()
. To this function, we need to pass the handles for all the threads on which we wish to wait. This function demands that we store these handles in an array and pass this array as the second argument to the function WaitForMultipleObjects()
. So we define an array Array_Of_Thread_Handles[3]
. We store the handles of the three threads in this array and pass this array as the second argument to the function WaitForMultipleObjects()
. The first argument to this function indicates that we are waiting on the three threads. The third parameter is TRUE
. It indicates a wait for all the threads. The last parameter is passed as INFINITE
. This means, wait till all threads have completed.
Thus at this point, the main program waits for all the threads to complete their execution. In the mean time, the threads are running concurrently as is seen from the picture above. When all the threads are done, the control is transferred to the main program. The program prints the statement "Since all threads executed, let's close their handles" and goes ahead and closes the handles of the threads and exits.
I hope this program makes the creation of threads clear. Let me know if you have any questions. I hope I would be able to answer them.
History
- March 19, 2006: Initial release.