1 / 36

CS252: Systems Programming

CS252: Systems Programming. Ninghui Li Based on Slides by Prof. Gustavo Rodriguez-Rivera Topic 11: Thread-safe Data Structures, Semaphores. Pthread Overview. Pthreads : POSIX threads Mutual exclusion and synchronization tools that we cover Mutex locks Spin locks Read/write locks

duena
Download Presentation

CS252: Systems Programming

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.

E N D

Presentation Transcript


  1. CS252: Systems Programming Ninghui Li Based on Slides by Prof. Gustavo Rodriguez-Rivera Topic 11: Thread-safe Data Structures, Semaphores

  2. Pthread Overview • Pthreads: POSIX threads • Mutual exclusion and synchronization tools that we cover • Mutex locks • Spin locks • Read/write locks • Semaphores (not part of pthreads, but part of POSIX standards) • Condition variables: Suspend the execution of a thread until some pre-defined condition occurs

  3. Type of Mutex Locks • Depends on behavior in the following 2 cases • A: a thread that holds lock calls lock again • B: a thread that does not hold lock calls unlock • Recursive lock: A succeeds (locking n times requires unlocking n times); B returns error • pthread_mutexattr_init(&Attr); pthread_mutexattr_settype(&Attr, PTHREAD_MUTEX_RECURSIVE); • pthread_mutex_init(&Mutex, &Attr); • Error checking lock: returns error on A,B • Normal (fast) lock: deadlock on A; undefined on B • Implementation does not require remembering A,B • Default lock: undefined on A,B

  4. Which Type to Use • It is recommended to use Recursivein production systems when available. • When one has high confidence and full control of code, then using normal (fast) is fine • i.e., when one is certain that A and B cannot occur

  5. Behavior When Lock • Blocked until the lock is available. • Behavior of mutex lock • Yield CPU and try to obtain lock when scheduled, return when lock is available • Seems to be undesirable, since thread is off CPU, might as well wait until lock is available • Busy-waiting when lock is unavailable. • Behavior of SpinLock; don’t use one single CPU • Return “unable to obtain lock” immediately • To get this behavior in pthreads, use trylock • E.g., pthread_mutex_trylock()

  6. Threads and Fork • Example: • In Solaris one process creates 5 threads using thr_create and then calls fork() then the child will also have a copy of the 5 threads so we have a total of 10 threads • In Solaris (or other UNIX flavors) one process creates 5 threads using pthread create and then calls fork() therefore the child will only get one thread that is the thread that called fork so we have a total of 6 threads • Solaris has a system call fork1() that only copies the calling thread in the child process. • Modern UNIX use the pthread_create semantics that only copies the calling thread.

  7. Thread Safe and Race Condition Data structures and functions that are able to handle multiple threads are called “Thread Safe” or “Multi-Threaded (MT), or “Concurrent”. A Thread Unsafe function or data structure is one that when executed by multiple threads can result in logical errors. A bug related to having multiple threads modifying a data structure simultaneously is called a “Race Condition”. Race Conditions are difficult to debug because they are often difficult to reproduce.

  8. A Thread Unsafe List Class #include <pthread.h> struct ListEntry { int _val;ListEntry *_next; }; class List{ ListEntry *_head; public:List();void insert(int val);int remove(); } List::List(){ _head = NULL; } List::insert(intval){ ListEntry *e = new ListEntry; e->_val = val; a) e->_next = _head; b) _head = e; }

  9. A Thread Unsafe List Class int List::remove(){ ListEntry *tmp; c) tmp = _head; if(tmp == NULL) { return -1; } d) _head=tmp->_next; intval=tmp->_val; delete tmp; return val; }

  10. Race Conditions in List We can have the following race condition: Assume T1 calls remove() and T2 calls insert(). Initially _head 1 2 NULL

  11. Race Conditions in List 2. T2 insert(5) ctxswitch 1. T1 remove() _head 1 2 _head 1 2 tmp1 NULL NULL tmp1 5 E2 1. T1: c) tmp=_head 2. T2: a) e2->next=_head 3. T2 insert(5) ctxswitch 4. T1 remove() 1 2 _head 1 2 tmp1 NULL tmp1 NULL _head 5 5 4. T1: d)_head=tmp->_next; Node 5 is lost!!! 3. T2: b) _head = e2

  12. Race Conditions in List Now find a race condition involving two insert. Now find a race condition involving two remove. Initially _head 1 2 NULL

  13. An Thread Safe List Class #include <pthread.h> struct ListEntry { int _val;ListEntry *_next; }; class MTList{ ListEntry *_head;pthread_mutex_t _mutex; public:MTList();void insert(int val);int remove(); } MTList::MTList(){ _head = NULL; pthread_mutex_init( &_mutex, NULL ); } MTList::insert(int val){ ListEntry *e = new ListEntry;e->_val = val;pthread_mutex_lock(&_mutex); a) e->_next = _head; b)_head = e; pthread_mutex_unlock(&mutex); }

  14. A Thread Safe List intMTList::remove(){ ListEntry *tmp;pthread_mutex_lock(&_mutex); c) tmp = _head;if(tmp == NULL) { pthread_mutex_unlock(&_mutex); return -1; }d) _head=tmp->_next; pthread_mutex_unlock(&mutex); intval=tmp->_val; delete tmp; return val; }

  15. An Additional Requirement in the List Class The MT List implementation described before returns -1 if the list is empty. Suppose that we would like an implementation where the remove waits if the List is empty. This behavior is naturally implemented using semaphores.

  16. Semaphores • Semaphores are a signaling mechanism. • A semaphore is initialized with an initial counter that is greater or equal to 0. • Conceptually indicating # of available resources • A sem_wait call decreases the counter, and block the thread if the value is negative. • A sem_post call increases the counter, and unblock one waiting thread

  17. Semaphore Calls • Declaration: • #include <semaphore.h> • sem_tsem; • Initialization: • sem_init (sem_t*sem, intpshared, unsigned int value); • Decrement Semaphore: • sem_wait ( sem_t*sem); • Increment Semaphore • sem_post (sem_t*sem); • Man pages • man sem_wait

  18. Semaphore Calls Semantics Pseudocode: sem_init(sem_t*sem, counter){ sem -> count = counter; } sem_wait(sem_t*sem){ sem-> count--; if(sem ->count < 0){ wait(); } }

  19. Semaphore Calls Semantics sem_post(sem_t*sem){ sem ->count++; if(there is a thread waiting){ wake up the thread that has been waiting the longest. } // one out and can let one in } Note: The mutex lock calls are omitted in the pseudocode for simplicity.

  20. Semaphores vs. Mutex • Semaphores are a signaling mechanism; not specifically for mutual exclusion • Semaphore can be initialized to any positive integer, and mutex is binary • Binary semaphore vs. Mutex • With mutex, only the thread holding the lock is supposed to unlock • With semaphore, any thread can call sem_post to wake up a blocking thread • In general, semaphore and mutex are different • One can use a binary semaphore to achieve the effect of mutex, if one is careful to ensure that sem_postis only called after sem_wait succeeds

  21. Use of Semaphore Counter • Mutex Lock Case: initial counter == 1 • Can achieve effect of Mutex Lock • N Resources Case: initial counter is n > 1 • Control access to a fixed number n of resources. Example: Access to 5 printers. 5 computers can use them. 6th computer will need to wait. • Wait for an Event Case: initial counter == 0 • Synchronization. Wait for an event. A thread calling sem_waitwill block until another threads sends an event by calling sem_post.

  22. Example Semaphore count=1 (Mutex Lock Case) Assume sem_tsem; sem_init(&sem, 1); T1 T2 sem_wait(&sem) sem->count--(==0) Does not wait .. ctxswitchsem_wait(&sem) sem->count--(==-1) if (sem->count < 0) wait (ctxswitch) .. sem_post(&sem) sem->count++(==0) wakeup T2 continue

  23. Example Semaphore count=3 (N Resources Case) Assume sem_tsem; sem_init(&sem, 3); (3 printers) T1 T2 T3 sem_wait(&sem) sem->count--(==2) Does not wait print .. sem_wait(&sem) sem->count--(==1) Does not wait print .. sem_wait(&sem) sem->count--(==0) Does not wait print

  24. Example Semaphore count=3 (N Resources Case) T4 T5 T1 sem_wait(&sem) sem->count--(==-1) wait sem_wait(&sem) sem->count--(==-2) Wait Finished printing sem_post(&sem) sem->count++(==-1) Wakeup T4 print

  25. Example Semaphore count=0 Wait for an Event Case Assume sem_tsem; sem_init(&sem, 0); T1 T2 // wait for event sem_wait(&sem) sem->count--(==-1) wait //Send event to t1 sem_post(&sem) sem->count++(==0) Wakeup T1 .. T1 continues

  26. A Synchronized List Class We want to implement a List class where remove() will block if the list is empty. To implement this class, we will use a semaphore “_emptySem” that will be initialized with a counter of 0. Remove() will call sem_wait(&_emptySem) and it will block until insert() calls sem_post(&emptySem). The counter in semaphore will be equivalent to the number of items in the list.

  27. A Synchronized List Class SynchList.cc SyncList:: SyncList(){ _ _head = NULL; pthread_mutex_init( &_mutex, NULL ); sem_init(&_emptySem,0, 0); } SyncList ::insert(intval){ ListEntry *e = new ListEntry;e->_val = val;pthread_mutex_lock(&_mutex); a)e->_next = _head; b)_head = e; pthread_mutex_unlock(&_mutex); sem_post(&_emptySem); } SyncList.h #include <pthread.h> structListEntry { int _val;ListEntry *_next; }; class SyncList{ ListEntry *_head; pthread_mutex_t _mutex; sem_t_emptySem; public:SyncList();void insert(intval);int remove(); };

  28. A Synchronized List intSyncList ::remove(){ ListEntry *tmp; // Wait until list is not empty sem_wait(&_emptySem); pthread_mutex_lock(&_mutex); c)tmp= _head;d)head=tmp->_next; pthread_mutex_unlock(&mutex); intval=tmp->_val; delete tmp; return val; }

  29. Example: Removing Before Inserting T1 T2 remove() sem_wait(&_emptySem) (count==-1) wait insert(5) pthread_mutex_lock() a) b) pthread_mutex_unlock() sem_post(&_emptySem) wakeup T1 T1 continues pthread_mutex_lock() c) d) pthread_mutex_unlock()

  30. Example: Inserting Before Removing T1 T2 insert(7) pthread_mutex_lock() a) b) pthread_mutex_unlock() sem_post(&_emptySem)(count==1) Starts running remove() sem_wait(_emptySem); continue (count==0) pthread_mutex_lock() c) d) pthread_mutex_unlock()

  31. Notes on Synchronized List Class We need the mutex lock to enforce atomicity in the critical sections a) b) and c) d). The semaphore is not enough to enforce atomicity.

  32. Notes on Synchronized List Class intMTList::remove(){ ListEntry *tmp; sem_wait(&_emptySem); pthread_mutex_lock(&_mutex); c)tmp = _head; d)head=tmp->_next; pthread_mutex_unlock(&mutex); intval=tmp->_val; delete tmp; return val; } >N N 1 (N= items in list)

  33. Notes on Synchronized List Class The sem_wait() call has to be done outside the mutex_lock/unlock section. Otherwise we can get a deadlock. Example: pthread_mutex_lock(&_mutex); sem_wait(&_empty); c)tmp = _head; d)head=tmp->_next; pthread_mutex_unlock(&mutex);

  34. Notes on Synchronized List Class T1 T2 remove() mutex_lock sem_wait(&_empty) wait for sem_post from T2 insert(5) pthread_mutex_lock() wait for mutex_unlock in T1 Deadlock!!!!

  35. Review Questions How do Recursive Mutex locks differ from regular Mutex locks? Why one may prefer to use a mutex lock that is not resursive? How to implement recursive mutex locks (part of Project 4)? Why one should not use spin locks on a single CPU machone? When a process with multiple threads call fork(), what threads are created in child process, under pthread semantics?

  36. Review Questions What is a race condition? (When given simple codes, should be able to come up with race condition scenario.) What happens when calling sem_wait(), sem_post? (Should be able to produce pseudo-code.) Different usage of semaphore when initiating it with different values. What are the key difference between binary semaphore and mutex lock? How to implement a synchronized list? Why we need both mutex and semaphore?

More Related