Title: Chapter 12 POSIX Threads
1Chapter 12POSIX Threads
- Source Robbins and Robbins, UNIX Systems
Programming, Prentice Hall, 2003.
212.1 12.2 Case Study Processing Data from
Multiple Files
3Multiple File Processing
- Imagine that you need to write a program that
reads data from one or more files, where the
processing steps upon reading the data are the
same for each file - In a non-threaded approach, a blocking read
operation on any of the files causes the calling
process to block until input becomes available - Such blocking creates difficulties when a process
expects input from more then one source, since
the process has no way of knowing which file
descriptor will produce the next input - The multiple file reading problem commonly
appears in client/server programming because the
server expects input from multiple clients - Of the various approaches that can be used to
monitor multiple files, both blocking and
non-blocking strategies can lead to either
complicated code or busy waiting - One promising approach uses a separate thread to
handle each file, in effect reducing the problem
to one of processing a single file - Multiple threads can simplify the problem of
processing multiple files because a dedicated
thread with relatively simple logic can handle
the processing of each file - Threads also make the overlap of I/O and
processing transparent to the programmer - The next four slides show a program that uses a
thread approach to solve the problem
4MultipleFiles Program
// Usage a.out file_name_1 file_name_2
... include ltfcntl.hgt include
ltstdio.hgt include ltunistd.hgt include
ltpthread.hgt define MAX_FILES 5 define
BUFFER_SIZE 80 void processFileTable(int
files, int fileCount) void processFile(void
fileDescriptorPtr) void processData(char
data, int dataSize)
(More on next slide)
5MultipleFiles Program (continued)
//
int main(int argc, char argv) int
fileTableMAX_FILES int i int
nbrFiles nbrFiles argc - 1 for (i 1 i lt
nbrFiles i) fileTablei - 1
open(argvi, O_RDONLY) if (fileTablei - 1
-1) perror("Failed to open file")
// End for processFileTable(fileTable,
nbrFiles) return 0 // End main
(More on next slide)
6MultipleFiles Program (continued)
void processFileTable(int files, int fileCount)
int status int i pthread_t
threadTableMAX_FILES for (i 0 i lt
fileCount i) status
pthread_create(threadTable i, NULL,
processFile, (files i)) if (status ! 0)
fprintf(stderr, "Failed to create
thread d\n", i) threadTablei
pthread_self() // Mark failed threads //
End for // End for for (i 0 i lt
fileCount i) if (pthread_equal(pthread
_self(), threadTablei)) // Check failed
threads continue status
pthread_join(threadTablei, NULL) if (status
! 0) fprintf(stderr, "Failed to join
thread d s\n", i, strerror(status)) //
End for return // End processFileTable
(More on next slide)
7MultipleFiles Program (continued)
//
void processFile(void
fileDescriptorPtr) char bufferBUFFER_SIZE
int inFile ssize_t nbrBytes inFile ((int
)(fileDescriptorPtr)) for ( )
nbrBytes read(inFile, buffer, BUFFER_SIZE)
if (nbrBytes lt 0) break
processData(buffer, nbrBytes) // End
for return NULL // End processFile //
void processData(char data, int
dataSize) write(STDOUT_FILENO, data,
dataSize) // End processData
812.3a Basic Thread Functionality
9Thread Package
- A thread package usually includes functions for
thread creation and thread destruction,
scheduling, and enforcement of mutual exclusion - A typical thread package also contains a runtime
system to manage threads transparently (i.e., the
user is unaware of the runtime system) - When a thread is created, the runtime system
allocates data structures to hold the thread's
ID, registers, stack, and program counter value - The threads for a process share the entire
address space of that process - They can modify global variables, access open
file descriptors, and cooperate and interfere
with each other in many ways - POSIX threads are often called pthreads because
all of the thread functions start with pthread - POSIX threads are referenced by an ID of type
pthread_t - Most POSIX functions return zero if successful
otherwise, they return a nonzero error - They do not set errno and do not need to be
restarted if interrupted by a signal - The table on the next slide summarizes the basic
POSIX thread management functions
10POSIX Thread Functions
Name Description
pthread_attr_init Initialize a thread attribute object
pthread_cancel Terminate another thread
pthread_create Create a thread
pthread_detach Set thread to release resources
pthread_equal Test two thread IDs for equality
pthread_exit Exit a thread without exiting the process
pthread_kill Send a signal to a thread
pthread_join Wait for a thread to terminate
pthread_self Find out own thread ID
11pthread_attr_init() Function
- include ltpthread.hgtint pthread_attr_init(pthrea
d_attr_t attributes) - This function initializes a thread attribute
object with the default settings for each
attribute as summarized below - A thread may be joined by other threads
- Scheduling parameters, policy, and scope are
inherited from the creating thread - Priority is set to default for the scheduling
policy as determined by the system - The thread is scheduled system wide
- The stack size is inherited from the process
stack size attribute - If successful, the function returns zero
otherwise, it returns an error value
12pthread_self() Function
- A thread can find out its ID by calling
pthread_self()include ltpthread.hgtphtread_t
pthread_self(void) - The function returns the thread ID of the calling
thread - No errors are defined for this function
13pthread_equal() Function
- Since pthread_t may be a structure, a program
should use pthread_equal() to compare thread IDs
for equalityinclude ltpthread.hgtint
pthread_equal(pthread_t threadA, pthread_t
threadB) - If threadA equals threadB, the function returns a
nonzero value otherwise, it returns zero - No errors are defined for this function
- Example Usepthread_t currentThreadif (
pthread_equal( pthread_self(), currentThread) )
printf("The current thread ID matches my thread
ID\n")
14pthread_create() Function
- The pthread_create() function creates a
threadinclude ltpthread.hgtint
pthread_create(pthread_t thread, const
pthread_attr_t attributes, void
(start_routine(void ), void argument) - The thread parameter points to the ID of the
newly-created thread - The attributes parameter represents an attribute
object for the thread - If attributes is NULL, the new thread has the
default attributes - The start_routine parameter is the name of the
function that the thread calls when it begins
execution - The function must take a single parameter of type
void - The function must return a pointer of type void
, which is treated as an exit status The
argument parameter is the argument passed to the
start_routine function - If successful, the function returns zero
otherwise, it returns a nonzero error code - To pass multiple values as a parameter to a
thread, a pointer to an array or structure can be
used - Unlike some thread facilities, such as those
provided by the Java programming language, the
pthread_create function automatically makes the
thread runnable without requiring a separate
start operation
15Example use of pthread_create()
include ltfcntl.hgt include ltstdio.hgt include
ltunistd.hgt include ltpthread.hgt void
performOperation(void filePtr) int
main(void) int status int inFile pthread_t
threadID inFile open ("sample.dat",
O_RDONLY) // Error checking removed status
pthread_create(threadID, NULL, performOperation,
inFile) if (status ! 0) fprintf(stderr,
"Failed to create thread s\n",
strerror(status)) else fprintf(stderr, "The
thread was created\n") return 0 // End
main //
void performOperation(void filePtr)
// End performOperation
16pthread_detach() Function
- When a thread exits, it does not release its
resources unless it is a detached thread - The pthread_detach() function sets a thread's
internal options to specify that storage for the
thread can be reclaimed when the thread
exitsinclude ltpthread.hgtint
pthread_detach(pthread_t thread) - The pthread_detach() function has a single
parameter, thread, the thread ID of the thread to
be detached - If successful, the function returns zero
otherwise, it returns a nonzero error code - Detached threads do not report their status when
they exit
17Example use of pthread_detach()
- This code segment creates and then detaches a
threadstatus pthread_create(threadID, NULL,
doTask, dataFile) - if (status ! 0) fprintf(stderr, "Failed to
create thread\n") - else
-
- status pthread_detach(threadID)
- if (status ! 0)
- fprintf(stderr, "Failed to detach
thread\n") - // End else
- When the function below is called as a thread, it
detaches itselfvoid performOperation(void
argument) -
- int i ((int )(arg))
- status pthread_detach(pthread_self())
- if (status ! 0) return NULL
- fprintf(stderr, "Argument d\n", i)
- // End performOperation
18pthread_join() Function
- Threads that are not detached are joinable and do
not release all of their resources until another
thread calls pthread_join() for them or the
entire process exits - The pthread_join() function causes the calling
thread to wait for the specified thread to exit,
similar to waitpid() at the process
levelinclude ltpthread.hgtint
pthread_join(pthread_t thread, void valuePtr) - The function suspends the calling thread until
the target thread, specified by the first
parameter, terminates - The valuePtr parameter provides a location for a
pointer to the return status that the target
passes to pthread_exit() or return - If valuePtr is NULL, the calling program does not
retrieve the target thread return status - If successful, pthread_join() returns zero
otherwise, it returns a nonzero error code - A nondetached thread's resources are not released
until another thread calls phtread_join() with
the ID of the terminating thread as the first
parameter - To prevent memory leaks, long-running programs
should eventually call either pthread_detach() or
pthread_join() for every thread
19Example use of pthread_join()
- The code segment below illustrates how to
retrieve the value passed to pthread_exit() by a
terminating threadint statusint
exitCodePtrpthread_t threadIDstatus
pthread_join(threadID, exitCodePtr)if (status
! 0) fprintf(stderr, "Failed to join
thread\n")else fprintf(stderr, "Exit code
d\n", exitCodePtr)
20pthread_exit() Function
- The pthread_exit() function causes the calling
thread to terminateinclude ltpthread.hgtvoid
pthread_exit(void valuePtr) - The valuePtr value is made available to a
successful pthread_join() - However, the valuePtr in pthread_exit() must
point to data that exists after the thread exits - Consequently, the thread should not use a pointer
to local data for valuePtr - A process can terminate by calling exit()
directly, by executing return from the main()
function, or by having one of the other process
threads call exit() - In any of these cases, all threads terminate
- If the main thread has no work to do after
creating other threads, it should either block
until all threads have completed or call
pthread_exit(NULL) - A call to exit() causes the entire process to
terminate - A call to pthread_exit() causes only the calling
thread to terminate - A thread that executes return from its top level
implicitly calls pthread_exit() with the return
value (a pointer) serving as the parameter to
pthread_exit() - A process will exit with a return status of zero
if its last thread calls pthread_exit()
2112.3b Canceling a Thread
22pthread_cancel() Function
- The pthread_cancel() function permits a thread to
request that another thread be canceledinclude
ltpthread.hgtvoid pthread_cancel(pthread_t
targetThread) - The targetThread parameter is the thread ID of
the thread to be cancelled - The function does not cause the calling thread to
block while the cancellation completes - Rather, pthread_cancel() returns after making the
cancellation request - If successful, pthread_cancel() returns zero
otherwise, it returns a nonzero error code - Threads can force other threads to return through
the cancellation mechanism - The reaction of a thread to a cancellation
request depends on its state and type
23pthread_setcancelstate() Function
- Cancellation can cause difficulties if a thread
holds resources such as an open file descriptor
that must be released before exiting - The pthread_setcancelstate() function changes the
cancel-ability state of the calling
threadinclude ltpthread.hgtint
pthread_setcancelstate(int state, int
oldState) - The state parameter specifies the new state to
set - The oldState parameter points to an integer for
holding the previous state - If successful, the function returns zero
otherwise, it returns a nonzero error code - If a thread has the PTHREAD_CANCEL_ENABLE state,
it receives cancellation requests - If the thread has the PTHREAD_CANCEL_DISABLE
state, the cancellation requests are held pending - By default, threads have the PTHREAD_CANCEL_ENABLE
state
24pthread_setcanceltype() Function
- There may be points in the execution of a thread
at which an exit would leave the program in an
unacceptable state - The pthread_setcanceltype() function changes the
cancel-ability type of a thread as specified by
its type parameterinclude ltpthread.hgtint
pthread_setcanceltype(int type, int oldType) - The oldType parameter is a pointer to a location
for saving the previous type - If successful, the function returns zero
otherwise, it returns a nonzero error code - The cancellation type allows a thread to control
the point when it exits in response to a
cancellation request - When its cancellation type is PTHREAD_CANCEL_ASYNC
HRONOUS, the thread can act on the cancellation
request at any time - When its cancellation type is PTHREAD_CANCEL_DEFER
RED, the thread acts on cancellation requests
only at specified cancellation points - By default, threads have the PTHREAD_CANCEL_DEFERR
ED type
25pthread_testcancel() Function
- A thread can set a cancellation point at a
particular place in the code by calling the
pthread_testcancel() function include
ltpthread.hgtvoid pthread_testcancel(void) - The function has no return value
- When its cancellation type is PTHREAD_CANCEL_DEFER
RED, the thread accepts pending cancellation
requests when it reaches such a cancellation
point - Certain blocking functions, such as read(), are
automatically treated as cancellation points - As a general rule, a function that changes its
cancellation state or its type should restore the
value before returning - A caller cannot make reliable assumptions about
the program behavior unless this rule is
observed
26Example use of pthread_setcancelstate()
void doTask(char command, int commandSize) void
processTask(void argument) char
bufferBUFFER_SIZE int inFile ssize_t
nbrBytes int status int newState int
oldState inFile ( (int ) arg) for ( )
nbrBytes read(inFile, buffer,
BUFFER_SIZE) if (nbrBytes lt 0) break
status pthread_setcancelstate(PTHREAD_CANCEL_D
ISABLE, oldState) if (status ! 0)
return argument doTask(buffer, nbrBytes)
status pthread_setcancelstate(oldstate,
newstate) if (status ! 0) return
argument // End for return NULL // End
processTask
27(Added) Case Study Parallel Approach for
Performing A Building Search
28Building Search Program
- Imagine that you need to write a program that
demonstrates the simultaneous search of all
floors in a building in order to find a person - This can be done by assigning a specific thread
to search each floor - After searching a floor, a thread reports back to
the main thread that it either found the person
hiding in a certain room on the floor or that
nobody was found after searching the entire floor - As soon as a thread announces that it found the
person, the main thread sends a request to all
remaining threads (that are still searching) to
cancel their search - The program begins by reading the command line to
find out how many floors are in the building - The program then picks a random floor and room to
hide the person it is assumed that only one
person is hidden in the building - The program continues by creating a thread to
search each floor simultaneously - The program ends as soon as the hidden person is
found
29Building Search Program
include ltstdio.hgt include ltpthread.hgt include
lttime.hgt define MAX_FLOORS 1000 define
MAX_SIDE_LENGTH 10 define MAX_BUFFER_SIZE
40 define FALSE 0 define TRUE 1 void
hidePerson(int nbrFloors) void
searchBuilding(int nbrFloors) void
searchFloor(void floorPtr) static int
buildingMAX_FLOORSMAX_SIDE_LENGTHMAX_SIDE_LEN
GTH
(More on next slide)
30Building Search Program
int main(int argc, char argv) int floorCount
0 if (argc ! 2) fprintf(stderr,
"\nUsage a.out floors(1-d)\n", MAX_FLOORS)
return 1 // End if else floorCount
atoi(argv1) srand(time(0)) hidePerson(floorC
ount) searchBuilding(floorCount) return 0
// End main
(More on next slide)
31Building Search Program
void hidePerson(int nbrFloors) int floor int
row int column int room for (floor 0 floor
lt nbrFloors floor) for (row 0 row lt
MAX_SIDE_LENGTH row) for (column 0
column lt MAX_SIDE_LENGTH column)
buildingfloorrowcolumn FALSE // Pick a
random location to hide the person floor rand()
nbrFloors row rand() MAX_SIDE_LENGTH colum
n rand() MAX_SIDE_LENGTH buildingfloorrow
column TRUE room row 10
column fprintf(stderr, "\n(Hiding location)
Floor d Room d\n\n", floor, room)
// End hidePerson
(More on next slide)
32Building Search Program
void searchBuilding(int nbrFloors) int
status int floor int i int j pthread_t
threadTablenbrFloors int locationnbrFloors i
nt foundPtr // Create a thread to search each
floor for (floor 0 floor lt nbrFloors
floor) locationfloor floor
status pthread_create(threadTable floor,
NULL, searchFloor,
location floor) if (status ! 0)
fprintf(stderr, "Failed to create thread d
s\n", floor, strerror(status))
return // End if // End for
(More on next slide)
33Building Search Program
// Check the search results returned by each
thread for (i 0 i lt nbrFloors i)
status pthread_join(threadTablei, (void
)foundPtr) if (status ! 0)
fprintf(stderr, "Failed to join thread d s\n",
i, strerror(status)) else if (! (foundPtr) )
// Check for a false condition
fprintf(stderr, "(Floor d) Nobody was found\n",
i) else if (foundPtr lt 10) //
Adjust for insertion of zero in room number
fprintf(stderr, "(Floor d) Found person in
Room d0d\n", i, i,
foundPtr) else fprintf(stderr,
"(Floor d) Found person in Room dd\n",
i, i, foundPtr) for (j i 1
j lt nbrFloors j) pthread_cancel(thread
Tablej) fprintf(stderr, "\nAll
remaining searches have been canceled\n")
break // End else // End
for return // End searchBuilding
(More on next slide)
34Building Search Program
//
void searchFloor(void
floorPtr) char bufferMAX_BUFFER_SIZE int
floor int row int column int i int
resultPtr floor ((int )(floorPtr)) for
(i 0 i lt MAX_BUFFER_SIZE i) bufferi
'\0' // Null byte resultPtr (int
)malloc(sizeof(int))
(More on next slide)
35Building Search Program
// Check each room on the floor for (row 0 row
lt MAX_SIDE_LENGTH row) for (column 0
column lt MAX_SIDE_LENGTH column)
pthread_testcancel() if (buildingfloorrow
column) resultPtr row 10
column return resultPtr // End
if // End for resultPtr FALSE return
resultPtr // End searchFloor
36Sample Program Output
uxb2 a.out 20 (Hiding location) Floor 14
Room 78 (Floor 0) Nobody was found (Floor 1)
Nobody was found (Floor 2) Nobody was
found (Floor 3) Nobody was found (Floor 4) Nobody
was found (Floor 5) Nobody was found (Floor 6)
Nobody was found (Floor 7) Nobody was
found (Floor 8) Nobody was found (Floor 9) Nobody
was found (Floor 10) Nobody was found (Floor 11)
Nobody was found (Floor 12) Nobody was
found (Floor 13) Nobody was found (Floor 14)
Found person in Room 1478 All remaining searches
have been canceled uxb2
3712.4 Thread Safety
38Thread Safety
- A hidden problem with threads is that they may
call library functions that are not thread-safe,
possibly producing spurious results - A function is thread-safe if multiple threads can
execute simultaneous active invocations of the
function without interference - POSIX specifies that all the required functions,
including the functions from the standard C
library, be implemented in a thread-safe manner - There are certain exceptions to this requirement
some are listed below - asctime(), ctime(), getenv(), localtime(),
rand(), readdir(), setenv(), strerror(), strtok() - In traditional UNIX implementations, errno is a
global external variable that is set when system
functions produce an error - This implementation does not work for
multithreading, and in most thread
implementations errno is a macro that returns
thread-specific information - In essence, each thread has a private copy of
errno - The main thread does not have direct access to
errno for a joined thread, so if needed, this
information must be returned through the last
parameter of pthread_join()
3912.5 User Threads versus Kernel Threads
40User-Level Threads
- The two traditional models of thread control are
user-level threads and kernel-level threads - User-level threads usually run on top of an
existing operating system - These threads are invisible to the kernel and
compete among themselves for the resources
allocated to their encapsulating process - The threads are scheduled by a thread runtime
system that is part of the process code - Programs with user-level threads usually link to
a special library in which each library function
is enclosed in a jacket - The jacket function calls the thread runtime
system to do thread management before and
possibly after calling the jacketed library
function - Functions such as read() and sleep() can present
a problem for user-level threads because they may
cause the process to block - To avoid blocking the entire process on a
blocking call, the user-level thread library
replaces each potentially blocking call in the
jacket by a nonblocking version
41User-Level Threads (continued)
- User-level threads have low overhead, but they
also have some disadvantages - The user-level thread model, which assumes that
the thread runtime system will eventually regain
control, can be thwarted by CPU-bound threads - A CPU-bound thread rarely performs library calls
and may prevent the thread runtime system from
regaining control to schedule other threads - The user-level thread model can share only
processor resources allocated to the
encapsulating process - This restriction limits the amount of available
parallelism because the threads can run on only
one processor at a time - Since one of the prime motivations of using
threads is to take advantage of multiprocessor
workstations, user-level threads alone are not an
acceptable approach
42Kernel-level Threads
- With kernel-level threads, the kernel is aware of
each thread as a schedulable entity and threads
compete system-wide for processor resources - The scheduling of kernel-level threads can be
almost as expensive as the scheduling of
processes themselves, but kernel-level threads
can take advantage of multiple processors - The synchronization and sharing of data for
kernel-level threads is less expensive than for
full processes, but kernel-level threads are
considerably more expensive to manage than
user-level threads
43Hybrid Thread Models
- Hybrid thread models have advantages of both
user-level and kernel-level models by providing
two levels of control - The programmer writes the program in terms of
user-level threads and then specifies how many
kernel-schedulable entities are associated with
the process - The user-level threads are mapped into the
kernel-schedulable entities at runtime to achieve
parallelism - The level of control that a user has over the
mapping depends on the implementation - For example, in the Sun Solaris thread
implementation, the user-level threads are called
threads and the kernel-schedulable entities are
called lightweight processes - The user can specify that a particular thread be
run by a dedicated lightweight process or that a
particular group of threads be run by a pool of
lightweight processes - The POSIX thread scheduling model is a hybrid
model that is flexible enough to support both
user-level and kernel-level threads in particular
implementations of the standard - The model consists of two levels of scheduling
threads and kernel entities - The threads are analogous to user-level threads
- The kernel entities are scheduled by the kernel
- The thread library decides how many kernel
entities it needs and how they will be mapped
?