Presentation is loading. Please wait.

Presentation is loading. Please wait.

Programming with POSIX* Threads

Similar presentations


Presentation on theme: "Programming with POSIX* Threads"— Presentation transcript:

1 Programming with POSIX* Threads
Intel Software College

2 Programming with POSIX* Threads
Objectives At the completion of this module you will be able to Write code to create and terminate POSIX* threads Use Pthreads synchronization objects to coordinate thread execution and memory access Programming with POSIX* Threads

3 Programming with POSIX* Threads
Agenda Explore POSIX* Threading API functions Create and manage threads Synchronization objects Attributes and thread specific data Labs to give hands-on experience Programming with POSIX* Threads

4 Programming with Explicit Threads
Identify tasks for threading Identify computational model What work is to be done? What algorithm to use? Identify how data will be accessed What is global? What is local? How is data to be assigned to threads? Programming with POSIX* Threads

5 Coding Considerations
Create function to encapsulate computation May be a function that already exists If so, driver function may be needed to coordinate multiple threads Pthreads allows single parameter to thread function C structure for multiple arguments Programming with POSIX* Threads

6 More Coding Considerations
Recast parameter to local variable if needed May be structure of several parameters Add code to determine task of thread May need access to global variable Add code to access data for task Add code to protect global variables Shared data access may need to be restricted Add code to synchronize thread executions Programming with POSIX* Threads

7 Task Allocation Methods
Static Initial assignment sets work Computations or data are divided equally based on number of threads and thread ID Dynamic Useful if tasks require unequal or unknown execution time Work given to threads as computation proceeds Programming with POSIX* Threads

8 Programming with POSIX* Threads
What is Pthreads? POSIX.1c standard C language interface Threads exist within same process All threads are peers No explicit parent-child model Exception: “main thread” holds process information Programming with POSIX* Threads

9 Programming with POSIX* Threads
Thread Data Types pthread_t thread handle pthread_attr_t thread attributes: detach state stack size stack address specifying NULL gives default attributes Programming with POSIX* Threads

10 Programming with POSIX* Threads
Using Pthreads Identify portions of code to thread Encapsulate code into function If code is already a function, a driver function may need to be written to coordinate work of multiple threads Add pthread_create() call to assign thread(s) to execute function Programming with POSIX* Threads

11 Programming with POSIX* Threads
pthread_create int pthread_create(tid, attr, function, arg); pthread_t *tid handle of created thread const pthread_attr_t *attr attributes of thread to be created void *(*function)(void *) function to be mapped to thread void *arg single argument to function Programming with POSIX* Threads

12 pthread_create Explained
Spawn a thread running the function Thread handle returned via pthread_t structure Specify NULL to use default attributes Single argument sent to function If no arguments to function, specify NULL Check error codes! EAGAIN - insufficient resources to create thread EINVAL - invalid attribute Programming with POSIX* Threads

13 Example: Thread Creation
#include <stdio.h> #include <pthread.h> void *hello () { printf(“Hello Thread\n”); } main() { pthread_t tid; pthread_create(&tid, NULL, hello, NULL); Two possible outcomes: Message “Hello Thread” is printed on screen Nothing printed on screen. This outcome is more likely that previous. Main thread is the process and when the process ends, all threads are cancelled, too. Thus, if the pthread_create call returns before the O/S has had the time to set up the thread and begin execution, the thread will die a premature death when the process ends. What Happens? Programming with POSIX* Threads

14 Programming with POSIX* Threads
Example Explained Main thread is process When process goes, all threads go Need some method of waiting for a thread to finish (This slide says what was in the Notes on the previous slide. It is a take home of the explanation given while the previous slide was being displayed.) To avoid writing applications that spawn threads and then end before any useful work has the chance to begin, we need some mechanism to wait for threads to finish their processing. Programming with POSIX* Threads

15 Waiting for POSIX* Threads
#include <stdio.h> #include <pthread.h> int threadDone = 0; void *hello () { printf(“Hello Thread\n”); threadDone = 1; } main() { pthread_t tid; pthread_create(&tid, NULL, hello, NULL); while (!threadDone); Not a good idea! (This slide shows conceptually (and simply) the needed functionality that is in pthread_join() ) While the message will eventually be printed out, the main thread is sitting is a spin-wait until the value of threadDone is changed by the created thread. Of course, at this point, the thread has completed all needed computation and if the thread is killed by the process ending rather than by natural causes, we have not lost any work. However, if running on a single processor system (or with HT), the main thread may be spinning it’s wheels, soaking up thousands or millions of CPU cycles before the OP/S swaps it out and allows the thread to execute. Obviously, this is not a good idea. // wasted cycles! Programming with POSIX* Threads

16 Programming with POSIX* Threads
Waiting for a Thread int pthread_join(tid, val_ptr); pthread_t tid handle of joinable thread void **val_ptr exit value returned by joined thread This is the better way to have one thread wait for the completion of another thread. Pthread_join will block until the thread associated with the pthread_t handle has terminated. The second parameter returns a pointer to a value from the thread being joined. This value can be “sent” from the joined thread by use of return or pthread_exit(). The type of the returned value is (void *) since this is the return type of the function that was used in the pthread_create call. pthread_join() can be used to wait for one thread to terminate. There is no single function that can join multiple threads. Programming with POSIX* Threads

17 pthread_join Explained
Calling thread waits for thread with handle tid to terminate Only one thread can be joined Thread must be joinable Exit value is returned from joined thread Type returned is (void *) Use NULL if no return value expected ESRCH - thread (pthread_t) not found EINVAL - thread (pthread_t) not joinable Programming with POSIX* Threads

18 Programming with POSIX* Threads
Thread States Pthreads threads have two states joinable and detached Threads are joinable by default Resources are kept until pthread_join Can be reset with attributes or API call Detached threads cannot be joined Resources can be reclaimed at termination Cannot reset to be joinable Pthread_join detaches the thread automatically, so resources can be reclaimed at that time. This would be why threads can only be joined once during the execution. Once a thread is detached, whether by attributes or API call, that thread cannot be set to be joinable. Programming with POSIX* Threads

19 Example: Multiple Threads
#include <stdio.h> #include <pthread.h> #define NUM_THREADS 4 void *hello () { printf(“Hello Thread\n”); } main() { pthread_t tid[NUM_THREADS]; for (int i = 0; i < NUM_THREADS; i++) pthread_create(&tid[i], NULL, hello, NULL); pthread_join(tid[i], NULL); Better example of waiting for threads, in this case, multiple threads doing the same function. (Point out function calls used and what they are doing and their interactions.) Notice that there must be one call for each thread needed to be “joined” after termination. Also, the joins are done in the order of the thread’s creation. Thus, if the last thread created is the first to finish, it will not be joined until the previous threads have finished. Programming with POSIX* Threads

20 Programming with POSIX* Threads
LAB #1: “HelloThreads” Modify the previous example code to print out Appropriate “Hello Thread” message Unique thread number Use for-loop variable of pthread_create() loop Example output: Hello from Thread #0 Hello from Thread #1 Hello from Thread #2 Hello from Thread #3 Programming with POSIX* Threads

21 Programming with POSIX* Threads
What’s wrong? What is printed for myNum? void *threadFunc(void *pArg) { int* p = (int*)pArg; int myNum = *p; printf( “Thread number %d\n”, myNum); } . . . // from main(): for (int i = 0; i < numThreads; i++) { pthread_create(&tid[i], NULL, threadFunc, &i); Problem is passing *address* of “i”; value of “i” is changing and will likely be different when thread is allowed to run than when pthread_create was called. Time thread0 thread1 thread2 index 0: created 0 1: created 1 2: get *arg // value of *arg is 2 Etc. Programming with POSIX* Threads

22 Hello Threads Timeline
main Thread 0 Thread 1 T0 i = 0 --- ---- T1 create(&i) T2 i++ (i == 1) launch T3 p = pArg T4 i++ (i == 2) myNum = *p myNum = 2 T5 wait print(2) T6 exit Again this is just an example to emphasize timing issues. Notice that both threads (0, 1) get the same value for “num” because they both de-reference the same address for “myNum”. It is very possible in this scenario that thread0 gets a value of myNum = 1, but thread1 will get a value of num = 2. But this is still incorrect; correct would be for the threads to get values of 0 and 1, respectively. Mention that this is called a “data race” when more than one thread accesses the same variable. Programming with POSIX* Threads

23 Programming with POSIX* Threads
Race Conditions Concurrent access of same variable by multiple threads Read/Write conflict Write/Write conflict Most common error in concurrent programs May not be apparent at all times Programming with POSIX* Threads

24 Programming with POSIX* Threads
How to Avoid Data Races Scope variables to be local to threads Variables declared within threaded functions Allocate on thread’s stack TLS (Thread Local Storage) Control shared access with critical regions Mutual exclusion and synchronization Lock, semaphore, condition variable, critical section, mutex… Programming with POSIX* Threads

25 Solution – “Local” Storage
void *threadFunc(void *pArg) { int myNum = *((int*)pArg); printf( “Thread number %d\n”, myNum); } . . . // from main(): for (int i = 0; i < numThreads; i++) { tNum[i] = i; pthread_create(&tid[i], NULL, threadFunc, &tNum[i]); Solve the problem of passing *address* of “i” by saving current value of “i” in location that will not change. Be sure each thread gets pointer to unique element of tNum array. Programming with POSIX* Threads

26 Programming with POSIX* Threads
Pthreads* Mutex Simple, flexible, and efficient Enables correct programming structures for avoiding race conditions New types pthread_mutex_t the mutex variable pthread_mutexattr_t mutex attributes Before use, mutex must be initialized The Pthread mutex object. New data types used to declare objects. Mutex must first be initialized before it can be used. Mutex can only be “held” by one thread at a time. If asked, mutexes can be shared between processes, but only if the Pthreads system supports the functionality. Programming with POSIX* Threads

27 Programming with POSIX* Threads
pthread_mutex_init int pthread_mutex_init( mutex, attr ); pthread_mutex_t *mutex mutex to be initialized const pthread_mutexattr_t *attr attributes to be given to mutex Can also use the static, default initializer PTHREAD_MUTEX_INITIALIZER Programmer must pay attention to mutex scope ENOMEM - insufficient memory for mutex EAGAIN - insufficient resources (other than memory) EPERM - no privilege to perform operation Programming with POSIX* Threads

28 Alternate Initialization
Can also use the static initializer PTHREAD_MUTEX_INITIALIZER Uses default attributes Programmer must always pay attention to mutex scope Must be visible to threads pthread_mutex_t mtx1 = PTHREAD_MUTEX_INITIALIZER; Static initialization is done at declaration. Programming with POSIX* Threads

29 Programming with POSIX* Threads
pthread_mutex_lock int pthread_mutex_lock( mutex ); pthread_mutex_t *mutex mutex to attempt to lock Programming with POSIX* Threads

30 pthread_mutex_lock Explained
Attempts to lock mutex If mutex is locked by another thread, calling thread is blocked Mutex is held by calling thread until unlocked Mutex lock/unlock must be paired or deadlock occurs EINVAL - mutex is invalid EDEADLK - calling thread already owns mutex Programming with POSIX* Threads

31 pthread_mutex_unlock
int pthread_mutex_unlock( mutex ); pthread_mutex_t *mutex mutex to be unlocked EINVAL - mutex is invalid EPERM - calling thread does not own mutex Programming with POSIX* Threads

32 Programming with POSIX* Threads
Example: Use of mutex #define NUMTHREADS 4 pthread_mutex_t gMutex; // why does this have to be global? int g_sum = 0; void *threadFunc(void *arg) { int mySum = bigComputation(); pthread_mutex_lock( &gMutex ); g_sum += mySum; // threads access one at a time pthread_mutex_unlock( &gMutex ); } main() { pthread_t hThread[NUMTHREADS]; pthread_mutex_init( &gMutex, NULL ); for (int i = 0; i < NUMTHREADS; i++) pthread_create(&hThread[i],NULL,threadFunc,NULL); pthread_join(hThread[i]); (Point out features and function calls of example code) Q: Why not just put bigComputation() into critical region? A: Thread would exclude all other threads from running their own, independent calls to bigComputation. This would make the code serial. Programming with POSIX* Threads

33 Numerical Integration Example
4.0 4.0 (1+x2) dx =  1 4.0 (1+x2) f(x) = static long num_steps=100000; double step, pi; void main() { int i; double x; step = 1.0/(double) num_steps; for (i=0; i< num_steps; i++){ x = (i+0.5)*step; pi += 4.0/(1.0 + x*x); } pi *= step; printf(“Pi = %f\n”,pi); 2.0 0.0 X 1.0 Programming with POSIX* Threads

34 Programming with POSIX* Threads
Lab 2: Computing Pi static long num_steps=100000; double step, pi; void main() { int i; double x; step = 1.0/(double) num_steps; for (i=0; i< num_steps; i++){ x = (i+0.5)*step; pi += 4.0/(1.0 + x*x); } pi *= step; printf(“Pi = %f\n”,pi); Parallelize the numerical integration code using POSIX* Threads How can the loop iterations be divided among the threads? What variables can be local? What variables need to be visible to all threads? This is a serial version of the source code. It does not use a “sum” variable that could give a clue to an efficient solution (i.e., local partial sum variable that is updated each loop iteration). This code is small and efficient in serial, but will challenge the students to come up with an efficient solution. Of course, efficiency is not one of the goals with such a short module. Getting the answer correct with multiple threads is enough of a goal for this. Programming with POSIX* Threads

35 Programming with POSIX* Threads
Condition Variables Semaphores are conditional on the semaphore count Condition variable is associated with an arbitrary conditional Same operations: wait and signal Provides mutual exclusion Students may have heard of a semaphore, especially if they have studied Operating Systems in school. This box is meant to call out the difference between a semaphore and a condition variable. The semaphore is conditioned on the value of the semaphore (zero or non-zero), while condition variables can be triggered on any arbitrary condition the programmer cares to write. Mutual exclusion is provided by having threads wait on the condition variable until signaled. Once signaled and woken up, if the conditional expression evaluates correctly, a thread will proceed; otherwise, the thread should be directed to return to waiting on the condition variable. Programming with POSIX* Threads

36 Condition Variable and Mutex
Mutex is associated with condition variable Protects evaluation of the conditional expression Prevents race condition between signaling thread and threads waiting on condition variable Condition variables are always paired with a mutex. The mutex is used to protect access any variables that are used in the conditional expression. This access will be in testing the conditional and updating variables that are involved in the conditional test. In other words, the mutex must protect the code from race conditions between a thread signaling the condition variable with a thread waiting on the condition variable. The only job of the mutex should be to protect variables used in the conditional. This will ensure proper utilization of condition variables and prevent lock contention performance problems when a mutex is overloaded by being used in other parts of the code. Programming with POSIX* Threads

37 Lost and Spurious Signals
Signal to condition variable is not saved If no thread waiting, signal is “lost” Thread can be deadlocked waiting for signal that will not be sent Condition variable can (rarely) receive spurious signals Slowed execution from predictable signals Need to retest conditional expression Two problems that can arise from using condition variables. Both of these are taken care of when using the algorithm on the next slide. Lost signal – condition has no memory, thus, if no thread is waiting on the condition variable, all signals on that condition variable will do nothing. If a thread “blindly” waits on a condition variable, it can be deadlocked if there is no other signal to wake it up. (Thus, the conditional expression is checked before a thread will wait.) Spurious wakeups – on some multi-processor systems, condition variable code could be slowed down if all signals were to be made predictable. (Thus, the correct algorithm requires a retest of the conditional expression after a thread is signaled after waiting on the condition variable. Spurious wakeups should put the thread back to waiting on the condition variable.) Programming with POSIX* Threads

38 Condition Variable Algorithm
Avoids problems with lost and spurious signals Negation of condition needed to proceed acquire mutex; while (conditional is true) wait on condition variable; perform critical region computation; update conditional; signal sleeping thread(s); release mutex; Mutex is automatically released when thread waits May be optional Conditional in while test is the negation of the condition needed to proceed in to the critical region. For example, if (x > 0) is needed to get past condition variable, test will be while (x<= 0). This is the most important part of the algorithm presented. The while test prevents… LOST SIGNALS since the conditional is tested before the thread waits. If the condition is false (able to proceed), the thread will not wait SPURIOUS WAKEUP since the thread will retest the while condition. If the condition is still true (not able to proceed), the thread will go back to waiting. [This is not prevented, but handled properly so that the code works as expected even when spurious wakeups occur.] As will be seen shortly, the mutex is released when the thread waits on the condition variable. The “update” and “signal” steps can be done external to the algorithm, dependent upon the requirements of the application. However, programmer must be sure the variables involved are updated while protected by the mutex associated with the condition variable. Programming with POSIX* Threads

39 Programming with POSIX* Threads
Condition Variables pthread_cond_init, pthread_cond_destroy initialize/destroy condition variable pthread_cond_wait attempt to hold condition variable pthread_cond_signal signal release of condition variable pthread_cond_broadcast broadcast release of condition variable Programming with POSIX* Threads

40 Condition Variable Types
Data types used pthread_cond_t the condition variable pthread_condattr_t condition variable attributes Before use, condition variable (and mutex) must be initialized The Pthread condition variable object. New data types used to declare objects. Condition variable must first be initialized before it can be used. Condition is used to have threads wait until some condition has been met. If asked, condition variables can be shared between processes (in shared memory), but only if the Pthreads implementation supports the functionality. Programming with POSIX* Threads

41 Programming with POSIX* Threads
pthread_cond_init int pthread_cond_init( cond, attr ); pthread_cond_t *cond condition variable to be initialized const pthread_condattr_t *attr attributes to be given to condition variable ENOMEM - insufficient memory for mutex EAGAIN - insufficient resources (other than memory) EBUSY - condition variable already intialized EINVAL - attr is invalid Can also use the static, default initializer PTHREAD_COND_INITIALIZER Programmer must pay attention to condition variable scope Programming with POSIX* Threads

42 Alternate Initialization
Can also use the static initializer PTHREAD_COND_INITIALIZER Uses default attributes Programmer must always pay attention to condition (and mutex) scope Must be visible to threads pthread_cond_t cond1 = PTHREAD_COND_INITIALIZER; Static initialization is done at decalaration. Also, any variables involved with the conditional expression must be visible to the threads Programming with POSIX* Threads

43 Programming with POSIX* Threads
pthread_cond_wait int pthread_cond_wait( cond, mutex ); pthread_cond_t *cond condition variable to wait on pthread_mutex_t *mutex mutex to be unlocked Programming with POSIX* Threads

44 pthread_cond_wait Explained
Thread put to “sleep” waiting for signal on cond Mutex is unlocked Allows other threads to acquire lock When signal arrives, mutex will be reacquired before pthread_cond_wait returns The calling thread will block (sleep) until such time as a pthread_cond_signal is issued that wakes up the threads. Upon going to sleep, the mutex (held because the thread has the mutex via the standard algorithm already presented) is unlocked. This will allow other threads that may be wanting to wait on the condition variable to enter the wait algorithm and for threads that need to update the variables used in the conditional expression the chance to lock the mutex to make changes. Before the pthread_cond_wait function returns (after the thread receives a signal), the mutex will be automatically reacquired (locked). This gives the thread mutually exclusive access to the conditional expression variables (if needed) and why the standard algorithm releases the lock when done. EINVAL - cond or mutex is invalid EINVAL - different mutex for concurrent waits EINVAL - calling thread does not own mutex Programming with POSIX* Threads

45 Programming with POSIX* Threads
pthread_cond_signal int pthread_cond_signal( cond ); pthread_cond_t *cond condition variable to be signaled Programming with POSIX* Threads

46 pthread_cond_signal Explained
Signal condition variable, wake one waiting thread If no threads waiting, no action taken Signal is not saved for future threads Signaling thread need not have mutex May be more efficient Problem may occur if thread priorities used If the signaling thread does not hold the associated mutex, the problem when using thread priorities would develop if a high priority thread is waiting and lower priority thread might lock the mutex (at the start of the condition variable algorithm) before the higher priority thread got the chance to reawaken and lock the mutex. By holding the mutex when signaling, in this situation, the lower priority thread will block in the attempt to lock the mutex and the higher priority thread will be given preference to acquire the mutex when it is released by the signaling thread. EINVAL - cond is invalid Programming with POSIX* Threads

47 Programming with POSIX* Threads
Example: Denominator Two threads oversee a global variable Thread 1 calculates the value Thread 2 needs a non-zero value A mutex controls access to the variable Thread 1 signals thread 2 (waiting) Programming with POSIX* Threads

48 Programming with POSIX* Threads
Example: Denominator #include <pthread.h> pthread_mutex_t denom_mtx = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t denom_cond = PTHREAD_COND_INITIALIZER; float denominator = 0.0; /* global */ void *thread1() { pthread_mutex_lock( &denom_mtx ); denominator = f(); /* calculate denominator */ pthread_signal( &denom_cond ); /* signal waiting thread */ pthread_mutex_unlock( &denom_mtx ); } Thread 1 is straight-forward, under protection of the mutex, assign a value to the denominator, then signal thread 2 and unlock the mutex. Notice static initializers for both the condition variable and the mutex. Programming with POSIX* Threads

49 Programming with POSIX* Threads
Example: Denominator void *thread2() { float local_denom; pthread_mutex_lock( &denom_mtx ); /* wait for non-zero denominator */ while( denominator == 0.0 ) pthread_cond_wait( &denom_cond, &denom_mtx ); local_denom = denominator; pthread_mutex_unlock( &denom_mtx ); /* Use local copy of denominator for division */ } This code follows condition variable algorithm (without any change to conditional expression nor a re-signal to condition variable). Questions to ask students for comprehension of the dynamics of condition variables: Q1. What happens if Thread 1 locks mutex first? A1. Thread 2 blocks at pthread_mutex_lock, when Thread 1 releases lock, Thread 2 gets lock, executes while test for FALSE (denominator != 0.0) and proceeds. [The signal is lost, but the while test prevents Thread 2 from waiting and deadlock.] Q2. What happens if Thread 2 locks mutex first? A2. Thread 1 blocks at lock, Thread 2 checks while conditional, finds TRUE, calls pthread_cond_wait to block and release mutex. Thread 1 gets mutex, sets denominator, signals condition variable, releases mutex. Thread 2 wakes up, when mutex is available, pthread_cond_wait returns, Thread 2 checks while conditional as FALSE, and proceeds. Q3. Why have all the overhead of a mutex and condition variable? Why not just have Thread 2 use a spin loop: while (denominator == 0.0) {} ? A3. This can be done, but the spin lock can take up execution resources and block other threads from execution, especially on HT systems. Programming with POSIX* Threads

50 pthread_cond_broadcast
int pthread_cond_broadcast( cond ); pthread_cond_t *cond condition variable to signal Programming with POSIX* Threads

51 pthread_cond_broadcast Explained
Wake all threads waiting on condition variable If no threads waiting, no action taken Broadcast is not saved for future threads Signaling thread need not have mutex Each thread waiting on the condition variable will be signaled and, in turn, as the mutex becomes available, return from the pthread_cond_wait call. EINVAL - cond is invalid Programming with POSIX* Threads

52 Denominator: Many threads
Assume multiple threads created on Thread2() What happens if… all threads are waiting? no threads are waiting? only some threads are waiting? void *thread1() { pthread_mutex_lock( &denom_mtx ); denominator = f(); pthread_cond_broadcast( &denom_cond ); /* wake all */ pthread_mutex_unlock( &denom_mtx ); } Thread2() code remains the same; Thread1 code is modified as above. Answers to all questions are that everything works out just fine. Any waiting threads all wake up (as mutex acquisition allows) and pass through while-loop; any threads not waiting (have not gotten access to mutex, yet) will get mutex and pass over while loop. No deadlock. Programming with POSIX* Threads

53 Activity 3 – Condition variables
Replace spin-wait and thread counting variable with condition variables to signal thread completion of computational piece Programming with POSIX* Threads

54 Programming with POSIX* Threads
Semaphores Synchronization object that keeps a count Represent the number of available resources Formalized by Edsgar Dijkstra Two operation on semaphores Wait [P(s)]: Thread waits until s > 0, then s = s-1 Post [V(s)]: s = s + 1 From Wikipedia (“Semaphore (programming),” revised 07 FEB 06): P and V are initialisms of Dutch words. The explanation for V is simple, it means "verhoog," or increase. Several explanations have been given for P in Dutch ("passeer" for pass, "probeer" for try, "pakken" for grab), but all these are incorrect. In fact Dijkstra intended P to stand for a made-up word "prolaag,"[1] short for "probeer te verlagen," or try-and-decrease.[2][3] This confusion stems from the unfortunate characteristic of the Dutch language that the words for increase and decrease both begin with the letter V, and the words spelled out in full would be impossibly confusing for non-Dutch speakers. Programming with POSIX* Threads

55 Programming with POSIX* Threads
POSIX Semaphores Not part of the POSIX Threads specification Defined in POSIX.1b Check for system support of semaphores before use Is _POSIX_SEMAPORES defined in <unistd.h>? If available, threads within a process can use semaphores Use header file <semaphore.h> New type sem_t the semaphore variable Programming with POSIX* Threads

56 Programming with POSIX* Threads
sem_init int sem_init( sem, pshared, value ); sem_t *sem counting semaphore to be initialized int pshared if non-zero, semaphore can be shared across processes unsigned int value initial value of semaphore Function to initialize a semaphore to the given value. Programming with POSIX* Threads

57 Programming with POSIX* Threads
sem_init Explained Initializes the semaphore object If pshared is zero, semaphore can only be used by threads within the calling process If non-zero, semaphore can be used between processes The value parameter sets the initial semaphore count value Function to initialize a semaphore to the given value. EINVAL – sem is not valid semaphore EPERM – process lacks approriate privelege ENOSYS – semaphores not supported Programming with POSIX* Threads

58 Programming with POSIX* Threads
sem_wait int sem_wait( sem ); sem_t *sem counting semaphore to decrement or wait . Programming with POSIX* Threads

59 Programming with POSIX* Threads
sem_wait Explained If semaphore count is greater than zero Decrement count by one (1) Proceed with code following Else, if semaphore count is zero Thread blocks until value is greater than zero EINVAL – sem is not valid semaphore EDEADLK – deadlock condition was detected ENOSYS – semaphores not supported Programming with POSIX* Threads

60 Programming with POSIX* Threads
sem_post int sem_post( sem ); sem_t *sem counting semaphore to be incremented Function to initialize a semaphore to the given value. Programming with POSIX* Threads

61 Programming with POSIX* Threads
sem_post Explained Post a wakeup to semaphore If one or more threads are waiting, release one Otherwise, increment semaphore count by one (1) Function to initialize a semaphore to the given value. EINVAL – sem is not valid semaphore ENOSYS – semaphores not supported Programming with POSIX* Threads

62 Programming with POSIX* Threads
Semaphore Uses Control access to limited size data structures Queues, stacks, deques Use count to enumerate available elements Throttle number of active threads within a region Binary semaphore [0,1] can act as mutex Implementing a queue with an array will limit the number of elements that can reside in the queue at any time. Initialize the semaphore with the maximum queue length. Use WAIT before placing something new on the queue. If the queue is full, this will block the thread attempting to place an item in the queue. Use POST when removing something off the queue; which will decrement the count and allow a waiting thread to proceed with placing an item in the queue. WHAT ABOUT EMPTY QUEUE? May use a semaphore to also keep track of the number of items in a queue. Thus, to enter something on queue, a thread must WAIT on full semaphore, deposit item, POST to empty queue semaphore. To remove an item, a thread must WAIT on empty queue semaphore, remove item, POST to full semaphore. IF performance will be affected by too many threads being active (perhaps on HT system?), a semaphore can be used to limit the number of threads executing within a given region of code. Binary semaphore can replace mutex and CRITICAL_SECTION, but there are potential problems that are pointed out on next slide. Programming with POSIX* Threads

63 Programming with POSIX* Threads
Semaphore Cautions No ownership of semaphore Any thread can release a semaphore, not just the last thread to wait Use good programming practice to avoid No concept of abandoned semaphore If thread terminates before post, semaphore increment may be lost Deadlock Programming with POSIX* Threads

64 Example: Semaphore as Mutex
Main thread opens input file, waits for thread termination Threads will Read line from input file Count all five letter words in line Programming with POSIX* Threads

65 Programming with POSIX* Threads
Example: Main sem_t hSem1, hSem2; FILE *fd; int fiveLetterCount = 0; main(){ pthread_t hThread[NUMTHREADS]; sem_init (*hSem1, 0, 1); // Binary semaphore sem_init (*hSem2, 0, 1); // Binary semaphore fd = fopen(“InFile”, “r”); // Open file for read for (int i = 0; i < NUMTHREADS; i++) pthread_create (&hThread[i], NULL, CountFives, NULL); pthread_join (hThread[i], NULL); fclose(fd); printf(“Number of five letter words is %d\n”, fiveLetterCount); } Programming with POSIX* Threads

66 Programming with POSIX* Threads
Example: Semaphores void * CountFives(void *arg) { int bDone = 0 ; char inLine[132]; int lCount = 0; while (!bDone) { sem_wait(*hSem1); // access to input bDone = (GetNextLine(fd, inLine) == EOF); sem_post(*hSem1); if (!bDone) if (lCount = GetFiveLetterWordCount(inLine)) { sem_wait(*hSem2); // update global fiveLetterCount += lCount; sem_post(*hsem2); } GetNextLine and GetFiveLetterWordCount will be user-defined functions (not shown here) that do the obvious thing Programming with POSIX* Threads

67 Activity 4 – Using Semaphores
Use binary semaphores to control access to shared variables Programming with POSIX* Threads

68 Some Advanced Functions
Thread-specific Data Thread Attributes Mutex Attributes Condition Variable Attributes Just a quick overview of functions Not too many details Programming with POSIX* Threads

69 Programming with POSIX* Threads
Thread-specific Data Another means for “local” storage pthread_key_create Create thread-specific key for all threads pthread_setspecific Associate thread-specific value with given key pthread_getspecific Return current data value associated with key pthread_key_delete Delete a data key Programming with POSIX* Threads

70 Thread Attribute Functions
pthread_attr_init Initialize attribute object to default settings pthread_attr_destroy Delete attribute object pthread_{get|set}detachstate Return or set thread’s detach state pthread_{get|set}stackaddr Return or set the stack address of thread pthread_{get|set}stacksize Return or set the stack size of thread Programming with POSIX* Threads

71 Mutex Attribute Functions
pthread_mutexattr_init Intialize mutex attribute object to defaults pthread_mutexattr_destroy Delete mutex attribute object pthread_mutexattr_{get|set}pshared Return or set whether mutex is shared between processes Programming with POSIX* Threads

72 Condition Attribute Functions
pthread_condattr_init Initialize condition variable attribute object pthread_condattr_destroy Destroy condition variable attribute object pthread_condattr_{get|set}pshared Return or set whether condition variable is shared between processes Programming with POSIX* Threads

73 Programming with POSIX* Threads
Summary Create threads to execute work encapsulated within functions Coordinate shared access between threads to avoid race conditions Local storage to avoid conflicts Synchronization objects to organize use Programming with POSIX* Threads

74 Programming with POSIX* Threads


Download ppt "Programming with POSIX* Threads"

Similar presentations


Ads by Google