1 / 43

Chapter 12 POSIX Threads

Chapter 12 POSIX Threads. Source: Robbins and Robbins, UNIX Systems Programming, Prentice Hall, 2003. 12.1 – 12.2 Case Study: Processing Data from Multiple Files. Multiple File Processing.

Download Presentation

Chapter 12 POSIX Threads

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.


Presentation Transcript

  1. Chapter 12POSIX Threads Source: Robbins and Robbins, UNIX Systems Programming, Prentice Hall, 2003.

  2. 12.1 – 12.2 Case Study: Processing Data from Multiple Files

  3. Multiple 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

  4. MultipleFiles Program // Usage: a.out file_name_1 [file_name_2 ...] #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <pthread.h> #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)

  5. MultipleFiles Program (continued) // ******************************************************* int main(int argc, char* argv[]) { int fileTable[MAX_FILES]; int i; int nbrFiles; nbrFiles = argc - 1; for (i = 1; i <= nbrFiles; i++) { fileTable[i - 1] = open(argv[i], O_RDONLY); if (fileTable[i - 1] == -1) perror("Failed to open file"); } // End for processFileTable(fileTable, nbrFiles); return 0; } // End main (More on next slide)

  6. MultipleFiles Program (continued) void processFileTable(int files[], int fileCount) { int status; int i; pthread_t threadTable[MAX_FILES]; for (i = 0; i < fileCount; i++) { status = pthread_create(threadTable + i, NULL, processFile, (files + i)); if (status != 0) { fprintf(stderr, "Failed to create thread %d\n", i); threadTable[i] = pthread_self(); // Mark failed threads } // End for } // End for for (i = 0; i < fileCount; i++) { if (pthread_equal(pthread_self(), threadTable[i])) // Check failed threads continue; status = pthread_join(threadTable[i], 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)

  7. MultipleFiles Program (continued) // ********************************************************** void *processFile(void *fileDescriptorPtr) { char buffer[BUFFER_SIZE]; int inFile; ssize_t nbrBytes; inFile = *((int *)(fileDescriptorPtr)); for ( ; ; ) { nbrBytes = read(inFile, buffer, BUFFER_SIZE); if (nbrBytes <= 0) break; processData(buffer, nbrBytes); } // End for return NULL; } // End processFile // ********************************************************** void processData(char *data, int dataSize) { write(STDOUT_FILENO, data, dataSize); } // End processData

  8. 12.3a Basic Thread Functionality

  9. Thread 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

  10. POSIX Thread Functions

  11. pthread_attr_init() Function • #include <pthread.h>int pthread_attr_init(pthread_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

  12. pthread_self() Function • A thread can find out its ID by calling pthread_self()#include <pthread.h>phtread_t pthread_self(void); • The function returns the thread ID of the calling thread • No errors are defined for this function

  13. pthread_equal() Function • Since pthread_t may be a structure, a program should use pthread_equal() to compare thread IDs for equality#include <pthread.h>int 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 currentThread;if ( pthread_equal( pthread_self(), currentThread) ) printf("The current thread ID matches my thread ID\n");

  14. pthread_create() Function • The pthread_create() function creates a thread#include <pthread.h>int 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

  15. Example use of pthread_create() #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <pthread.h> 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

  16. pthread_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 exits#include <pthread.h>int 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

  17. Example 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

  18. pthread_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 level#include <pthread.h>int 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

  19. Example use of pthread_join() • The code segment below illustrates how to retrieve the value passed to pthread_exit() by a terminating threadint status;int *exitCodePtr;pthread_t threadID;status = pthread_join(threadID, &exitCodePtr);if (status != 0) fprintf(stderr, "Failed to join thread\n");else fprintf(stderr, "Exit code: %d\n", *exitCodePtr);

  20. pthread_exit() Function • The pthread_exit() function causes the calling thread to terminate#include <pthread.h>void 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()

  21. 12.3b Canceling a Thread

  22. pthread_cancel() Function • The pthread_cancel() function permits a thread to request that another thread be canceled#include <pthread.h>void 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

  23. pthread_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 thread#include <pthread.h>int 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

  24. pthread_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 parameter#include <pthread.h>int 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_ASYNCHRONOUS, the thread can act on the cancellation request at any time • When its cancellation type is PTHREAD_CANCEL_DEFERRED, the thread acts on cancellation requests only at specified cancellation points • By default, threads have the PTHREAD_CANCEL_DEFERRED type

  25. pthread_testcancel() Function • A thread can set a cancellation point at a particular place in the code by calling the pthread_testcancel() function#include <pthread.h>void pthread_testcancel(void); • The function has no return value • When its cancellation type is PTHREAD_CANCEL_DEFERRED, 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

  26. Example use of pthread_setcancelstate() void doTask(char *command, int commandSize); void *processTask(void *argument) { char buffer[BUFFER_SIZE]; int inFile; ssize_t nbrBytes; int status; int newState; int oldState; inFile = *( (int *) arg); for ( ; ; ) { nbrBytes = read(inFile, buffer, BUFFER_SIZE); if (nbrBytes <= 0) break; status = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &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

  28. Building 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

  29. Building Search Program #include <stdio.h> #include <pthread.h> #include <time.h> #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 building[MAX_FLOORS][MAX_SIDE_LENGTH][MAX_SIDE_LENGTH]; (More on next slide)

  30. Building 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(argv[1]); srand(time(0)); hidePerson(floorCount); searchBuilding(floorCount); return 0; } // End main (More on next slide)

  31. Building Search Program void hidePerson(int nbrFloors) { int floor; int row; int column; int room; for (floor = 0; floor < nbrFloors; floor++) for (row = 0; row < MAX_SIDE_LENGTH; row++) for (column = 0; column < MAX_SIDE_LENGTH; column++) building[floor][row][column] = FALSE; // Pick a random location to hide the person floor = rand() % nbrFloors; row = rand() % MAX_SIDE_LENGTH; column = rand() % MAX_SIDE_LENGTH; building[floor][row][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)

  32. Building Search Program void searchBuilding(int nbrFloors) { int status; int floor; int i; int j; pthread_t threadTable[nbrFloors]; int location[nbrFloors]; int *foundPtr; // Create a thread to search each floor for (floor = 0; floor < nbrFloors; floor++) { location[floor] = 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)

  33. Building Search Program // Check the search results returned by each thread for (i = 0; i < nbrFloors; i++) { status = pthread_join(threadTable[i], (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 < 10) // Adjust for insertion of zero in room number fprintf(stderr, "(Floor %d) Found person in Room %d0%d\n", i, i, *foundPtr); else fprintf(stderr, "(Floor %d) Found person in Room %d%d\n", i, i, *foundPtr); for (j = i + 1; j < nbrFloors; j++) pthread_cancel(threadTable[j]); fprintf(stderr, "\nAll remaining searches have been canceled\n"); break; } // End else } // End for return; } // End searchBuilding (More on next slide)

  34. Building Search Program // ********************************************************** void *searchFloor(void *floorPtr) { char buffer[MAX_BUFFER_SIZE]; int floor; int row; int column; int i; int *resultPtr; floor = *((int *)(floorPtr)); for (i = 0; i < MAX_BUFFER_SIZE; i++) buffer[i] = '\0'; // Null byte resultPtr = (int *)malloc(sizeof(int)); (More on next slide)

  35. Building Search Program // Check each room on the floor for (row = 0; row < MAX_SIDE_LENGTH; row++) for (column = 0; column < MAX_SIDE_LENGTH; column++) { pthread_testcancel(); if (building[floor][row][column]) { *resultPtr = row * 10 + column; return resultPtr; } // End if } // End for *resultPtr = FALSE; return resultPtr; } // End searchFloor

  36. Sample 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%

  37. 12.4 Thread Safety

  38. Thread 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()

  39. 12.5 User Threads versus Kernel Threads

  40. User-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

  41. User-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

  42. Kernel-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

  43. Hybrid 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 

More Related