Anda di halaman 1dari 0

An Introduction to Multithreading

With Programming Examples in C


For the Windows NT Platform
By
Andrew Lenart

CS-384, The Design of Operating Systems


Submitted to Dr. Mark Sebern
April 24, 1998

Table of Contents
Table of Contents.2
Index of Figures and Listings.2
Introduction to Threads and Multithreading........3
What are Threads?..3
The Benefits of Multithreading..3
Drawbacks of Multithreading.4
Classes of Multithreading5
Cooperative versus Preemptive Multithreading.5
Multithreading in Windows NT..6
Background.6
Threads and the Microsoft Win32 API..7
Programming Example 1..10
Threads and the C Run-Time Library...11
Programming Example 2..12
Conclusion14
Works Cited.14

Index of Figures and Code Listings


Figure 1: How Win32 fits into Windows NT.8
Listing 1: Programming Example 1 .10
Listing 2: Programming Example 2 .12

Introduction to Threads and Multithreading


What is a thread?

One of the most important things an operating system is responsible for is


the effective scheduling of processes (programs in execution). Each process can
be broken down into independently schedulable sequences of instructions, also
known as a threads of execution. Threads differ from processes in that they
carry smaller contexts, where a context is simply a state of execution. The
context of a thread is usually limited to a program counter, stack pointer, register
set, and some private data. Processes operate out of unique virtual address spaces,
and their contexts additionally include things like address translation tables, input
and output descriptors, and large amounts of data. Because threads have smaller
contexts, it is easy to see why they are also known as light-weight processes. In
contrast, heavy-weight processes are fully independent, containing everything
necessary to execute on their own.

The Benefits of Multithreading

Modern operating systems allow multiple processes to exist at the same


time, and each process can contain more than one thread of execution. In a
multithreaded system (containing 2 or more threads), each thread is effectively a
copy of its original process, but unlike heavyweight processes, these threads share
resources and global variables(Marrin 77). However, even though threads are
schedulable entities, it does not necessarily mean they execute in parallel.

Threads can be scheduled concurrently on multiple processor systems, while they


are time-sliced on single processor systems.
Compared to processes, threads can provide several benefits in realtime
applications. The most noteworthy advantage is speed. Because threads carry
less context, they can be created, terminated, and switched between much faster
than processes. Another advantage, which also comes with smaller size, is greater
control of their priority. For example, low-priority functions such as networking
can be taken out of the kernel and placed into a thread. This allows low-priority
thread scheduling for networking, and frees up the rest of the kernel for more
important things, which would not be possible using processes alone. A third
advantage of threads is more efficient communication, which can happen because
they operate out of the same address space, and share code and data. Processes
are located in separate address spaces, so communication requires sluggish
copying operations between them.

Drawbacks of Multithreading

While the use of multiple threads can increase the flexibility and
parallelism over a single thread, it also adds a certain amount of overhead in the
form of additional scheduling and context switching(Marrin 78). For
multithreading to be beneficial, the run time of each individual thread must be
long compared to the time it would take to switch between them.

Another fact that is of particular interest to programmers is that


multithreaded programs are more difficult to debug and trace execution through,
because threads share the same code and data. Also, data that was corrupted by
one thread could impact all other threads in a process, making it difficult to
associate the problem with a particular thread. However, if the threads were
designed to assist one another (known as cooperative multithreading), no
protection would be needed.
Even with these drawbacks, chances are good that multithreading will be
helpful for programs that contain code which can be executed in the background,
and can run independently of whatever happens in the foreground, requiring no
user intervention. Some applications for the effective use of multiple threads are
in data manipulation and kernel operations.

Classes of Multithreading
Cooperative versus Preemptive

One obvious requirement of multithreading is that the individual threads


that make up a process must be switched between at some point. This is
necessary because only one thread can have the CPU at a time for execution.
Switching between threads can either be cooperative or preemptive. In
cooperative thread switching, a thread runs until it decides it is done, then lets
other threads run, eventually returning to the caller. Preemptive task switching
involves a thread that runs until some event (like an interrupt) causes the thread to

be suspended and another thread to resume execution. In other words, in a


preemptive scheme, the time a thread gets to run (the quantum) is determined by
the operating system. The differences between these two styles of multithreading
are more important than they may seem. For example, if you wrote an application
assuming nothing would change during the processing of a Windows message, a
disaster could be imminent if the application was run on a preemptive operating
system. The thread may lose its timeslice in the middle of processing the
message, and data could be corrupted or lost.
Another difference between preemptive and cooperative multithreading
(and perhaps more obvious) is that in a cooperative system, the smallest
schedulable unit is the instance of the application itself(Finger 48). In a
preemptive system, this small unit becomes the thread.

Multithreading in Windows NT
Background

Microsofts Windows NT operating system, which will be the subject of


several programming examples as a case study, uses preemptive multithreading.
If a system has a single processor, Windows NT handles timeslicing of threads in
a process almost entirely by itself. Each thread is allowed to execute for some
length of time before being preempted by Windows NT, which switches
execution to another thread. Every process in Windows NT must have at least

one thread before it can execute, so even switching between processes means
switching between threads.
The objective of coding a multithreaded program is to make each thread
think it runs exclusively, while in reality this is not the case. As mentioned
earlier, each thread has its own context record. To a thread, the context record is
the data it has to preserve to resume execution in the same state it was in before
losing control over the CPU. In Windows NT, the context of a thread consists of
a client identification number, two stacks, the state of the processor, and its own
private data. However, Windows NT handles context switching such that the
threads dont need to know what state they are in, from a programmers
perspective.

Threads and The Microsoft Win32 API

To illustrate how multithreading is actually used in the programming of


applications for Windows NT, a simple example program has been provided in
Listing 1. The example is written in C, and uses the Microsoft Win32 32-bit
application programming interface (API) (see figure 1). In Windows API
programming, system resources like files and threads are represented by
structures internal to the operating system called objects. Windows NT will not
allow an application to access this structure directly, but requires the application
to obtain an object handle to examine or modify it. A handle is usually just a
pointer.

Figure 1: How Win32 fits into Windows NT(Prasad, 174).

Though the example program is simple, it contains elements that are


critical to multithreading. In the program, the user will be prompted to enter 100
numbers sequentially. The program will save the values to a file, and then use
those values to compute output to another file. 10 will be added to each number
in the file containing the original user values. In a program that was not
multithreaded, the 10 would not be added to the user data until they had entered
all 100 values. This is a perfect place to use multithreading, because a thread
could be used to do the adding in the background as the user entered data, and all
processing on the information would be finished immediately after the last
number was entered. It should be noted that the code in listing 1 uses a total of
two threads, one that the Windows NT loader created, and one created to do the

adding in the background. Also, some details like parameters to the functions that
handle file I/O have been omitted to keep the focus on threads.
If the Win32 API is used to implement multithreading, the actual threads
are created with a call to CreateThread. To get a better feel for exactly what
Windows NT needs to create a thread, you can look at the six parameters used in
this call. The first parameter is a pointer to a SECURITY_ATTRIBUTES
structure, which contains a security descriptor (to control sharing of the thread
between other processes). A value of NULL means no security is needed. The
second parameter to CreateThread is a stack size, which is necessary because each
thread runs on its own stack. Passing a value of zero here tells Windows NT to
make the stack the same size as that of the threads process stack. The third
parameter is the address of the thread function, where execution will begin, and
the fourth argument is the thread function parameters. CreateThread expects the
thread function to be of type WINAPI. Fifth is an integer that can be either a
zero, where the thread will run as soon as it is created, or
CREATE_SUSPENDED, where the thread will be created, but will not run unless
it is resumed with a call to ResumeThread. The last parameter is the thread ID.
Using listing 1 as an example, notice that the thread is created with no security
attributes, the same stack size as its process, a pointer to the thread function with
no arguments, and will run immediately upon being created. After the thread has
been created, and the code for the thread has finished executing, the thread needs
to be closed, which is done in Windows NT with a call to CloseHandle.

Begin Listing 1

#include <stdio.h>
#include <windows.h> /* For the CreateThread prototype */
long WINAPI Add10(long); /* Function prototype */
HANDLE hUserFile; /*File that the user enters data into */
int BytesWritten, Count, DataValue;
void main(void)
{
int iID; /* Holds the thread ID for each thread */
HANDLE hThread;
/* Step 1: Let the user input some data. */
hUserFile = CreateFile("datafile",<parameters>);
for (Count = 0; Count < 100; Count++)
{
printf("Please enter next number: ");
scanf("%d", &DataValue);
}
WriteFile(hUserFile, &DataValue, sizeof(int),
&BytesWritten, NULL);
}
CloseHandle(hUserFile);
/* Dispatch a thread that does Step 2 (adds 10 to the data) */
hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Add10,
NULL,0,&iID);
if(!hThread) <process error condition, cant create>;
else CloseHandle(hThread); /* Closes thread after the thread
finishes execution of the thread function */
/* Step 3: Let the user enter more data, because well still be
executing in the thread*/
}
long WINAPI Add10(long lParam) /* The Thread Function */
{
HANDLE hThreadFile; /* handle to the thread-created file */
int iTemp; /* gets the original value type in, and then gets 10
added to it */
hUserFile = CreateFile("Input datafile.dat",...);
hThreadFile = CreateFile("Output datafile.dat",...);
/* Read in data from hUserFile here */
iTemp += 10;
/* Write result to hThreadFile */
}
End Listing 1

10

Threads and C Run-Time Library Functions

Under Windows NT, thread creation and management is not limited to Win32
API calls. What can also be used, and is perhaps a better alternative, is the C run-time
library (the thread functions are in process.h). Specifically, the functions
_BeginThreadNT and _endthread can be used in place of CreateThread and CloseHandle.
This allows better performance for Windows NT applications, because the Win32 API is
a general 32-bit interface, and can be used on Windows 95 as well. CreateThread has
been proven to cause small memory leaks in Windows NT, which can build up over long
periods of thread execution. Also, _BeginThreadNT offers better control over thread
security. With these differences, however, the two functions take identical parameters.
To illustrate how _BeginThreadNT and _endthread can be used to implement
multithreading, another programming example has been provided (see listing 2). This
program creates 25 threads that are suspended on initial creation. As mentioned earlier, a
call to ResumeThread can be used to return each suspended process to the execution
state. Once all 25 threads is created and suspended, the program will prompt the user to
hit a key, which starts the execution of each thread, one-by-one. A call to
WaitForMultipleObjects will freeze the execution of code in main until each thread has
finished, and finally all thread handles will be closed.
Listing 2 shows that as the number of threads in a system increase, thread
synchronization becomes a bigger issue to the programmer.

11

Begin Listing 2

#include
#include
#include
#include

<windows.h>
<process.h>
<stdio.h>
<conio.h>

/* This function acts as the 'main' function for each new thread. */
static void threadMain(void *arg)
{
printf("Thread %2d has an ID of %u\n", (int)arg,
GetCurrentThreadId());
_endthread();
} /* End Function threadMain */
int main(void)
{
#define NTHREADS 25
HANDLE hThreads[NTHREADS];
Int i;
/* Create NTHREADS threads that are initially
suspended and that will run starting at threadMain(). */
for (i = 0; i < NTHREADS; i++)
{
SECURITY_ATTRIBUTES sa =
{
sizeof(SECURITY_ATTRIBUTES), /* structure size */
0, /* No security descriptor */
TRUE, /* Thread handle is inheritable*/
};
DWORD

threadId;

hThreads[i] = (HANDLE)_beginthreadNT(
threadMain, /* Thread starting address */
4096,
/* Thread stack size */
(void )i,
/* Thread start argument */
&sa,
/* Thread security */
CREATE_SUSPENDED, /* Create in
suspended state */
&threadId); // Thread ID.
if(hThreads[i] == INVALID_HANDLE_VALUE)
{
MessageBox(0,"Thread Creation Failed", Error",
MB_OK);
return 1;
}
printf("Created thread %2d with an ID of %u\n", i,
threadId);
} /* End loop through NTHREADS */
printf("\nPress a key to resume all threads\n\n");
getch();
/* Resume the suspended threads. */

12

for (i = 0; i < NTHREADS; i++)


ResumeThread(hThreads[i]);
/* Wait for all threads to finish execution. */
WaitForMultipleObjects
(NTHREADS, /* Number of objects to wait for */
hThreads, /* The objects to wait for */
TRUE,
/* Wait for all objects */
INFINITE); /* No timeout */
/* Now close all of the thread handles */
for
(i = 0; i < NTHREADS; i++)
CloseHandle(hThreads[i]);
return 0;
} /* End function main() */

End Listing 2

13

Conclusion
In recent years, the benefits of multithreading have become more apparent
to the end-user, and easier to implement software designers in the form of
operating systems that readily support it. This research paper has just scratched
the surface of the different techniques used in implementing multithreading in an
attempt to keep the CPU busy at all times, for maximum throughput of data in an
application.

Works Cited

Finger, Jonathan, Lightweight Tasks in C. Dr. Dobbs Journal April 1995: 48-50.
Prasad, Shashi, Weaving a Thread. Byte October 1995: 173-174.
Marrin, Ken, Multithreading Support Grows among Realtime Operating Systems.
Computer Design March 1993: 77-88.
Kerns, T., The Advantages of Multithreaded Applications. EE Evaluation Engineering
February 1998: 76, 78-79.
Silberschatz, Abraham, and Peter B. Galvin. Operating System Concepts.
Reading: Addison-Wesley Publishing Company, 1998.
Custer, Helen. Inside Windows NT. Redmond: Microsoft Press, 1993.

14

15

Anda mungkin juga menyukai