by June 16, 2001 0 comments



Ever since multithreading has been introduced, it has redefined how applications are built. In Win32, multithreading was introduced with the release of Windows 95. In this set of articles, we see what threads are and how to design a generic pool of worker threads to service multiple-client requests. 

What are threads?

Multithreading in Win32 has provided developers with one of the most required capabilities: executing more than one piece of code concurrently. One can find threads in use everywhere. In fact, a thread is now the basic unit of code being executed by the microprocessor. The OS schedules applications on a per thread basis, since this approach gives better control to execute the application. One of the most widely used examples of multithreading is installing an application. In this case, one thread takes care of copying the files to the hard disk in the background, while another keeps the ‘Cancel’ button active to let the user abort the installation if required. The thread copying the files in the background is called a worker thread, while the one that keeps the ‘Cancel’ button active is called the user-interface (UI) thread. While the UI thread processes Windows messages sent by the operating system (through which it gets to know that the ‘Cancel’ button has been pressed and the program should now abort), the worker threadonly deals with tasks not relating to user interaction.

Under Win32, the CreateThread API is used to create a thread. This has the following prototype (The text in color and brackets below is not part of the code):

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpSecurityAttributes, (SD)
DWORD dwStackSize,
(initial stack size) 
LPTHREAD_START_ROUTINE lpThreadFunctionAddress,(thread function)
LPVOID lpParameter,(thread argument)
DWORD dwCreationFlags, creation option)
LPDWORD lpThreadID (thread identifier)
);

The first parameter CreateThread is a pointer to the security descriptor for the client. This is used only under Windows NT. The second parameter specifies the size of the stack, in bytes, for the thread. If the parameter’s set to zero, Windows automatically sets the size to 1 MB usually. The third parameter is the address of the function that the thread will use for its execution. This function has a specific prototype as shown below.

DWORD WINAPI ThreadFunction(
LPVOID lpParameter
);

The thread function takes only void pointer as the parameter, which it usually casts to get the desired information from the referenced memory location. Also, it returns a 32-bit value, which is referred to as the return code of the thread. The return code is utilized as a success-failure indicator of the thread execution by the thread parent or the OS.

The fourth parameter to the API is the pointer to the data to be passed as the parameter to the thread function. The address passed here is received by the thread as its only argument. The fifth parameter is the thread-creation flag that tells you whether the thread upon creation should immediately start execution, or should be created in a suspended state using the CREATE_SUSPENDED flag.

Each thread created by Win32 has associated with it what is called the TID (Thread Information Database). TID contains all the details regarding the thread, like its priorities, debugging details, parent process, and more. The last parameter to CreateThread API is a pointer to a variable, which will get the ID of the thread created. This ID is used by Windows to get details pertaining to threads. 

If the thread is successfully created, Win32 returns a handle to the thread. This handle is used to work the open thread, like changing a threads priority. Now let’s see how a simple thread is created. Assuming that my thread function is defined as shown below,

DWORD MyThreadFunction(
LPVOID lpParam
)
{
DWORD *dwData = (DWORD *)lpParam;
printf(“From Thread >> Data passed to thread is : %ld\n”,*dwData);
return 0;
}

I would call the thread as follows.

void main(void)
{
DWORD dwData=2, tid;
HANDLE hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)MyThreadFunction, (LPVOID)&dwData, 0, &tid);
printf(“Thread creation %ssuccessful\n”,(!hThread)?”un”:””); 
}

If the thread creation fails, the handle to the thread is NULL, and that’s what we check to tell the user whether the thread creation was a success or not.

Now that we’ve gone through the basics of threads and their creation under Win32, let’s move to discussing worker threads and their importance, especially in Windows 9x. 

Why worker threads?

More often than not, the developer has to write an application for entertaining a large number of client requests. For example, suppose you have to write a multithreaded application to tell which of the first 10,000 numbers are even and which are odd. One of the possible pseudo-codes for this is:

for (i=0;i<10000;i++)
{
j=i+1; hThread=CreateThread(NULL,0,ThreadFunction,(LPVOID)&j,0,&tid);
}

Assuming all else is in order, this code shall pass the thread function, a pointer to the variable, which contains the number to be checked for being even or odd. The point here is that for 10,000 numbers, this code attempts to create 10,000 threads, becoming a resource-hungry demon in the process because the amount of OS resources utilized in creating and context-switching between the threads shall undermine the idea behind using the
threads–concurrency of code execution to make the application more responsive. The OS will spend more time switching between the threads than in performing the actual work. Moreover, in reality, this code is not likely to work beyond a few hundred or perhaps, a thousand threads.

The concept of worker thread pool is used in such situations. Here, the programmer creates a specific number of threads, say, 10 in number, which entertain all incoming client requests. When a client request comes in, the thread-selection algorithm selects an idle worker thread from the pool, and asks it to entertain the request. This continues until all the threads are busy working. If another client request comes in at this time, it is kept blocked until a timeout period (or even indefinitely) until a worker thread in the pool is free. Then, the free thread is assigned the blocked client request, and the process continues.

A worker thread pool is highly useful for designing scalable, multithreaded applications for Windows 9x since this set of OSs doesn’t expose functionality that’s present in Windows NT, called IO Completion Ports. This functionality is a highly optimized and more robust version of a worker thread pool, and it’s built into the OS itself.

Next month, we’ll design a worker thread pool using C++ and Win32 API. This generic worker thread pool class can be used in any multithreaded application where multiple client requests require similar handling. The code is already on the CD, so you can take a look and be ready for next month. Till then…

Kumar Gaurav Khanna runs www.wintools.2fs.com

No Comments so far

Jump into a conversation

No Comments Yet!

You can be the one to start a conversation.

Your data will be safe!Your e-mail address will not be published. Also other data will not be shared with third person.