Presentation is loading. Please wait.

Presentation is loading. Please wait.

Unix Synchronization * Mutexes * Semaphores - named - unnamed.

Similar presentations


Presentation on theme: "Unix Synchronization * Mutexes * Semaphores - named - unnamed."— Presentation transcript:

1 Unix Synchronization * Mutexes * Semaphores - named - unnamed

2 First we will study thread synchronization in Posix Posix thread synchronization is done with mutex locks

3 First, a quick review of threads...

4 operating systems Consider the following code #include void print_msg(char*); int main ( ) { print_msg(“hello “); print_msg(“world\n”); return 0; } void print_msg(char* m) { int i; for (i = 0; i < 5; i++) { printf(“%s”, m); fflush( stdout ); sleep(1); }

5 This program will produce hello hello hello hello hello world world operating systems

6 The thread of execution is main( ) print_msg( ) operating systems

7 With multiple threads of execution main( ) print_msg( ) operating systems

8 operating systems Consider the following code #include void *print_msg(void *); int main ( ) { pthread thr1, thr2; pthread_create(&thr1, NULL, print_msg, (void *)”hello “); pthread_create(&thr2, NULL, print_msg, (void *)”world\n “); pthread_join(thr1, NULL); pthread_join(thr2, NULL); return 0; } void print_msg(void* m) { char* cp = (char*) m; int i; for (i = 0; i < 5; i++) { printf(“%s”, cp); fflush( stdout ); sleep(1); }

9 This program will produce hello world operating systems

10 pthread_create(&thr1, NULL, print_msg, (void *)”hello “); start a new thread of execution. the address of the thread use default thread attributes the function to run on the thread the function parameter operating systems

11 pthread_join(thr1, NULL); wait here for this thread to finish the name of the thread where to store any return value operating systems

12 Thread Communication One of the advantages of threads is that they share the same address space. Threads can communicate by reading and writing to that shared space. operating systems

13 operating systems static int globalValue = 0; int main ( ) { int i; pthread_t mythread; pthread_create(&mythread, NULL, funny_add, NULL); for (i = 0; i < 10; i++) { globalValue++; sleep(1); } pthread_join(mythread, NULL); printf(“\nDone...global value = %d\n”, globalValue); return 0; } our shared variable

14 void *funny_add(void *arg) { int j; for (j = 0; j < 10; j++) { int temp = globalValue + 1; sleep(1); globalValue = temp; }

15 Using a shared variable main( ) funny_add( ) operating systems globalValue temp = globalValue + 1 sleep globalValue = temp globalValue++ sleep pthread_create pthread_join

16 This program seems to work okay, but it has a potential problem.... what is it? operating systems There is no thread synchronization. We cannot guarantee the order in which the threads are executed. Let’s Try It - exampleOne.c

17 Hmmmm.... Let’s add some print statements so that we can see how the threads are executing.

18 operating systems static int globalValue = 0; int main ( ) { int i; pthread_t mythread; pthread_create(&mythread, NULL, funny_add, NULL); for (i = 0; i < 10; i++) { fprintf(stderr, “Main thread, globalValue = %s\n”, globalValue); globalValue++; fprintf(stderr, “Main thread, incr globalValue = %s\n”, globalValue); sleep(1); } pthread_join(mythread, NULL); printf(“\nDone...global value = %d\n”, globalValue); return 0; } our shared variable

19 void *funny_add(void *arg) { int j; for (j = 0; j < 10; j++) { fprintf(stderr, “in funny_add, globalValue = %d\n”, globalValue); int temp = gloablValue + 1; sleep(1); globalValue = temp; fprintf(stderr, “in funny_add, incr globalValue = %d\n”, globalValue); }

20 MutexesMutexes A mutex is a special variable that can either be in a locked state or an unlocked state. If a mutex is locked, it has a distinguished thread that holds or owns the mutex. If no thread holds the mutex, it is unlocked, free, or available. A mutex also has a queue for threads that are waiting to hold the mutex. The order in which the threads in the queue obtain the mutex is determined by the OS’s thread scheduling policy. A mutex is a special variable that can either be in a locked state or an unlocked state. If a mutex is locked, it has a distinguished thread that holds or owns the mutex. If no thread holds the mutex, it is unlocked, free, or available. A mutex also has a queue for threads that are waiting to hold the mutex. The order in which the threads in the queue obtain the mutex is determined by the OS’s thread scheduling policy.

21 The mutex is the simplest and most efficient thread synchronization mechanism. A mutex is meant to be held for short periods of time. A thread that is waiting for a mutex is not logically interruptible, except by termination of the process or termination of the thread. Mutex locks are ideal for making changes to data structures in which the state of the data structure is temporarily inconsistent. The mutex is the simplest and most efficient thread synchronization mechanism. A mutex is meant to be held for short periods of time. A thread that is waiting for a mutex is not logically interruptible, except by termination of the process or termination of the thread. Mutex locks are ideal for making changes to data structures in which the state of the data structure is temporarily inconsistent.

22 In posix mutexes are declared with the data type pthread_mutex_t. A program must always initialize a mutex before it is used. A program can initialize a mutex in one of two ways: Creating and initializing a mutex using the static initializer PTHREAD_MUTEX_INITIALIZER this method is most efficient, and is guaranteed to only be executed exactly once before any threads begin execution. Use it for statically allocated mutex variables. calling pthread_mutex_init ( ) for dynamically allocated mutex variables. pthread_mutex_init (&my_lock, NULL);

23 Dynamic Initialization int pthread_mutex_init(pthread_mutex_t *mymutex, const pthread_mutexattr_t *attr); pointer to a dynamically allocated region of memory to store the mutex. pointer to mutex attributes okay to pass NULL when you initialize a mutex with pthead_mutex_init, you must destroy the mutex with pthread_mutex_destroy( ). Note that it does not free the memory used to store the mutex. returns 0 if successful

24 Static Initialization pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;

25 Locking and Unlocking a Mutex int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); All return 0 if successful Blocks until the mutex is available Returns immediately if the mutex is not available Releases the mutex

26 Let’s synchronize our threads with a mutex!

27 operating systems static int globalValue = 0; static pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER; int main ( ) { int i; pthread_t mythread; pthread_create(&mythread, NULL, funny_add, NULL); for (i = 0; i < 10; i++) { pthread_mutex_lock(&mymutex); fprintf(stderr, “Main thread, globalValue = %s\n”, globalValue); globalValue++; fprintf(stderr, “Main thread, incr globalValue = %s\n”, globalValue); pthread_mutex_unlock(&mymutex); sleep(1); } pthread_join(mythread, NULL); printf(“\nDone...global value = %d\n”, globalValue); return 0; }

28 A Volunteer Effort Locking and unlocking are voluntary and a program only achieves mutual exclusion when it’s threads correctly acquire the appropriate mutex when entering a critical section of code, and release the mutex when finished. One way of ensuring this is to only allow access to shared variables through well defined functions. Then put the mutex lock and unlock calls in these functions. The locking mechanism is then transparent to the calling threads. operating systems

29 Should I Always Use a Mutex? Consider a program containing a bunch of threads that execute the instruction myGlobalValue = myGlobalValue +1; Since this statement will likely generate a single machine instruction, should we worry about using a mutex? operating systems

30 Why?Why? Consider an SMP architecture that updates main memory in 32-bit blocks. Each processor may execute the instruction at the same time. The memory modification will then trickle down through level 1 and level 2 cache and then to main memory. If you are incrementing a 64 bit integer without mutexes, the uppermost 4 bytes might come from one processor and the lower 4 bytes from the other. Bummer! operating systems

31 How Many Mutexes If you place too many mutexes your code will slow down considerably. Use mutexes to serialize access to shared data. Don’t use them for local data. Don’t use them if your program logic ensures that only one thread can access shared data at a single time. If you place too many mutexes your code will slow down considerably. Use mutexes to serialize access to shared data. Don’t use them for local data. Don’t use them if your program logic ensures that only one thread can access shared data at a single time. operating systems

32 Protecting Unsafe Library Functions Many C library functions are not thread-safe. An example is the rand( ) function. However, you can safely use the rand( ) function in a multi-threaded environment if you can guarantee that no two threads are concurrently calling it. operating systems

33 int randsafe(double *ranp) { static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int error; if (error = pthread_mutex_lock(&lock)) return error; *ranp = (rand( ) +0.5) / (RAND_MAX + 1); return pthread_mutex_unlock(&lock); } operating systems

34 Suppose that we want to count the number of words in two separate files and add them together, using two threads to do the counting for us concurrently. operating systems Molay ch 14

35 Counting words with multiple threads of execution main( ) count_words( ) operating systems word_count file1 file2 word_count++

36 Suppose that the code generated for word_count++; looks something like load register, word_count add register, 1 store register, word_count operating systems

37 time thread 1 word_count 100 load reg, word_count 100 operating systems

38 time thread 1thread 2 word_count 100 load reg, word_count 100 load reg, word_count add register, 1 101 store reg, word_count operating systems

39 time thread 1thread 2 word_count 100 load reg, word_count 100 load reg, word_count add register, 1 101 store reg, word_count operating systems 100

40 time thread 1thread 2 word_count 101 load reg, word_count 100 load reg, word_count 100 add register, 1 101 store reg, word_count add register, 1 101 store reg, word_count operating systems

41 time thread 1thread 2 word_count 100 load reg, word_count 100 this thread gets blocked because the shared variable is locked add register, 1 101 store reg, word_count word_count 101 L O C K U N L O C K now this thread can run --- and access the shared variable word_count operating systems

42 You can solve this problem without using a mutex! Let each thread have its own counter. Add these counters at the end. operating systems

43 operating systems struct argset { char* fname; /* the file name */ int count; /* the word count */ };

44 pthread_t thr1; struct argset args1; args1.fname = argv[1]; args1.count = 0;... pthread_create(&thr1, NULL, count_words, (void *) &args1);... //print the sum of the two counters printf(“%5d total words\n”, args1.count + args2.count); pass in the struct as the argument

45 Now suppose your program is going to count the words in a very big file and a very small file. We would like to have some way for each thread to notify the main thread when it is done counting.

46 Using a Condition Variable we “signal” that there is something in the mailbox the mailbox has a lock on it to prevent two threads trying to access it at once.

47 Main Thread Counting Thread 1 Counting Thread 2 * set up a reporting mailbox - it has space for one count at a time - it has a flag that can be raised, it then snaps back - there is a mutex lock to protect the mailbox

48 Main Thread Counting Thread 1 Counting Thread 2 * unlock the mailbox and wait until the flag goes up

49 Main Thread Counting Thread 1 Counting Thread 2 * waits until it can lock the mailbox * if the mailbox is empty, it puts its count in the mailbox * It raises the flag on the mailbox * Then finally releases its lock

50 Main Thread Counting Thread 1 Counting Thread 2 * stops waiting when the flag goes up * locks the mailbox * takes out the count * processes the count * puts up the flag in case another count thread is waiting * unlocks the mailbox and waits

51 operating systems struct argset { char* fname; /* the file name */ int count; /* the word count */ };

52 struct argset *mailbox = NULL; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t flag = PTHREAD_COND_INITIALIZER: int main (int argc, char *argv[ ]) { pthread_t thr1, thr2; struct argset args1, args2; int reports_in = 0;... pthread_mutex_lock(&lock); args1.fname = argv[1]; args1.count = 0;... pthread_create(&thr1, NULL, count_words, (void *) &args1); lock the mutex. start a counting thread declaring and initializing the condition variable

53 One of the counting threads... pthread_mutex_lock(&lock); The counting thread tries to get the lock. It can’t, so it blocks

54 while (reports_in < 2) { pthread_cond_wait(&flag, &lock); this statement unlocks the mutex and blocks this thread until the flag is signaled. Back in the main thread... This is an atomic action!

55 One of the counting threads now gets the lock pthread_mutex_lock(&lock); if (mailbox != NULL) pthread_cond_wait(&flag, &lock); check to see if the mailbox is empty... if it is not, the thread has to wait until it is.

56 One of the counting threads now gets the lock pthread_mutex_lock(&lock); if (mailbox != NULL) pthread_cond_wait(&flag, &lock); mailbox = args; the mailbox is empty. store the address of this thread’s arguments in the mailbox

57 One of the counting threads now gets the lock pthread_mutex_lock(&lock); if (mailbox != NULL) pthread_cond_wait(&flag, &lock); mailbox = args; pthread_cond_signal(&flag); “Raise” the flag on the mailbox The main thread is waiting for this signal!

58 One of the counting threads now gets the lock pthread_mutex_lock(&lock); if (mailbox != NULL) pthread_cond_wait(&flag, &lock); mailbox = args; pthread_cond_signal(&flag); pthread_mutex_unlock(&lock); Finally, unlock the mutex.

59 while (reports_in < 2) { pthread_cond_wait(&flag, &lock); total_words += mailbox->count; if (mailbox == &args1) pthread_join(thr1, NULL); mailbox = NULL;... control returns to this point when the condition variable has been signaled and the mutex is available. This call automatically re-locks the mutex when it returns.

60 Semaphores

61 SemaphoresSemaphores There are two types of semaphores Binary semaphores – work just like a mutex Counting semaphores – use when you have a finite set of resources that is being used by a set of threads. When a thread needs a resource, it decrements the associated semaphore. When it is done with the resource, the thread increments the associated semaphore. Semaphore values cannot fall below zero. When a thread tries to use a semaphore, it is blocked if the semaphore is zero. There are two types of semaphores Binary semaphores – work just like a mutex Counting semaphores – use when you have a finite set of resources that is being used by a set of threads. When a thread needs a resource, it decrements the associated semaphore. When it is done with the resource, the thread increments the associated semaphore. Semaphore values cannot fall below zero. When a thread tries to use a semaphore, it is blocked if the semaphore is zero.

62 void wait (semaphore_t *sp) { if (sp->value > 0) sp->value--; else // sp->value is zero { add this thread to sp->list; block; } void signal (semaphore_t *sp) { if (sp->list != NULL) remove a thread from the list and put it in ready state; else sp->value++; } These are atomic operations! Pseudo-code to illustrate how wait and signal work

63 Semaphore Functions sem_init ( )Creates and initializes a semaphore sem_wait ( )decreases semaphore by one if it is non-zero. Otherwise, it waits sem_post ( )increases the semaphore by one sem_init ( )Creates and initializes a semaphore sem_wait ( )decreases semaphore by one if it is non-zero. Otherwise, it waits sem_post ( )increases the semaphore by one for Posix un-named semaphores. Note: OS X does not currently support unnamed semaphores!

64 Semaphore Functions Declaring a Semaphore sem_t *mySemaphore; Initializing the Semaphore sem_t *sem_open (const char *name, int flags, int perms, int value); Declaring a Semaphore sem_t *mySemaphore; Initializing the Semaphore sem_t *sem_open (const char *name, int flags, int perms, int value); for Posix named semaphores. The “name” of the semaphore Allows semaphores to be used when not sharing memory. O_CREAT | O_EXECL S_IRUSR | S_IWUSR | … Initial value

65 Semaphore Functions Waiting on a Semaphore int sem_wait(sem_t *sem); Signaling on a Semaphore int sem_post(sem_t *sem); Waiting on a Semaphore int sem_wait(sem_t *sem); Signaling on a Semaphore int sem_post(sem_t *sem); for Posix named semaphores. Both return 0 if successful If a semaphore is 0, the calling thread blocks until the semaphore becomes non-zero. Then it decrements the semaphore. If no threads are waiting, it increments the semaphore. If one or more threads are blocked, the value remains at zero and one of the waiting threads is unblocked.

66 Semaphore Functions Removing the semaphore from the system: int sem_unlink(const char *name); Removing the semaphore from the system: int sem_unlink(const char *name); for Posix named semaphores. Be sure that you include this function in your code to unlink any semaphores! Otherwise they persist beyond the lifetime of your program.

67 The sleeping Barber Problem

68 A classic Synchronization Problem There is one Barber and one Barber Chair There are n chairs in the waiting room

69 If there are no customers the barber sits in the chair and goes to sleep.

70 If there are no customers the barber sits in the chair and goes to sleep. When a customer enters the barbershop, they sit in the waiting room if there is a seat. Otherwise they stand and wait for a chair.

71 If the Barber’s chair is free, The customer wakes the barber and sits in the chair. When a customer enters the barbershop, they sit in the waiting room if there is a seat. Otherwise they stand and wait for a chair.

72 The sleeping Barber is a Consumer-Producer Problem

73 queue consumers producers producer and consumer threads share the queue and must lock the resource when inserting or removing items. When the queue is empty, consumers should block until an item becomes available. If the queue is full, producers should block until there is space to insert an item

74 The Circular Queue Problem … bufin – points to the next available slot bufout – points to the next item to be removed We need to make sure that a consumer cannot remove data if there is no data there. Otherwise it will get garbage. We need to make sure that a producer cannot write data when the buffer is full, otherwise it will write over existing good data.

75 bufin – points to the next available slot bufout – points to the next item to be removed void put_item(int item) { pthread_mutex_lock(&myLock); buffer[bufin] = item; bufin = (bufin + 1) % BUFSIZE; pthread_mutex_unlock(&myLock); } void get_item(int *item) { pthread_mutex_lock(&myLock); *item = buffer[bufout]; bufout = (bufout + 1) % BUFSIZE; pthread_mutex_unlock(&myLock); } Circular Queue when accessing the buffer, use a mutex to make sure that another thread is not accessing it at the same time. producer code consumer code

76 Initializing the semaphores sem_t items; sem_t slots; … sem_init(&items, 0, 0); sem_init(&slots, 0, BUFSIZE); declare the semaphore variables. items manages the number of items in the buffer ready for removal slots manages the number of buffer positions available for data when this parameter is 0 the semaphore is local to this process and can only be used by threads in this process. The initial value. For items start at 0, no items to remove slots starts at BUFSIZE, … all slots are available Un-named semaphores

77 The Producer Thread static void *producer(void *arg1) { int i; for (i = 1; i <= SUMSIZE; i++ ) { sem_wait(&slots); put_item(i*i); sem_post(&items); } return NULL; } the producer thread tries to get a buffer slot. If none is available (slots == 0), then the thread waits until one becomes available. When a slot becomes available ( slots > 0 ) sem_wait decrements slots and the thread proceeds to execute. after putting the data in the buffer, the producer thread increments the count of removable items by calling sem_post(&items), indicating that there is more data available for removal.. SUMSIZE is a globally defined constant that controls the number of items processed.

78 The Consumer Thread static void *consumer(void *arg2) { int i, myItem; for (i = 1; i <= SUMSIZE; i++ ) { sem_wait(&items); get_item(&myItem); sem_post(&slots); sum += myitem; } return NULL; } the consumer thread tries to get available data. If none is available (items == 0), then the thread waits until one becomes available. When data becomes available ( items > 0 ), the the sem_wait call decrements the count and the thread continues to execute.. after getting the data from the buffer, the consumer thread increments the count of open buffer slots by calling sem_post(&slots).

79 Question?Question? Will this program work correctly if there is more than one consumer thread? static void *consumer(void *arg2) { int i, myItem; for (i = 1; i <= SUMSIZE; i++ ) { sem_wait(&items); get_item(&myItem); sem_post(&slots); sum += myitem; } return NULL; } No! All consumer threads will try to process SUMSIZE items. Eventually the consumer threads will block, since the producer thread only produced SUMSIZE items,

80 The Producer Driven Problem The problem is that once a consumer thread blocks on a semaphore, there is no way to unblock the consumer Except by incrementing the semaphore with a sem_post. A producer who is finished cannot do a sem_post without making the consumers think that there is an item available to be removed.


Download ppt "Unix Synchronization * Mutexes * Semaphores - named - unnamed."

Similar presentations


Ads by Google