1 / 31

W4118 Operating Systems

Explore synchronization techniques like locks, semaphores, and monitors including their implementation methods and classical problems. Learn the importance of layered synchronization and efficient resource allocation strategies.

Download Presentation

W4118 Operating Systems

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. W4118 Operating Systems Instructor: Junfeng Yang

  2. Logistics • Homework 2 due time: 3:09 pm this Thursday (one hour before class) • Submit everything electronically at courseworks, including written assignment

  3. Last lecture • Synchronization • Layered approach to synchronization • Critical section requirements: safe, live, bounded • Desirable: efficient, fair, simple • Locks • Uniprocessor implementation: disable and enable interrupts • Software-based locks: peterson’s algorithm • Locks with hardware support • atomic test_and_set

  4. Today • Lock (wrap up) • Semaphore • Monitor • A classical synchronization problem: read and write lock

  5. Recall: Spin-wait or block • Spin-lock may waste CPU cycles: lock holder gets preempted, and scheduled threads try to grab lock • Shouldn’t use spin-lock on single core • On multi-core, good plan is: spin a bit, then yield

  6. Problem with simple yield lock() { while(test_and_set(&flag)) yield(); } • Problem: • Still a lot of context switches; poll for lock • Starvation possible • Why? No control over who gets the lock next • Need explicit control over who gets the lock

  7. Implementing locks: version 4 • The idea • Add thread to queue when lock unavailable • In unlock(), wake up one thread in queue • Problem I: may lose the wake up • Fix: use a spin_lock or lock w/ simple yield! • Doesn’t completely avoid spin-wait, but make wait time short, thus reasonable • Problem II: may not wake up the right thread • Fix: unlock() directly transfers lock to waiting thread lock() { if (flag == 1) add myself to wait queue yield … } unlock() { flag = 0 if(any thread in wait queue) wake up one wait thread … } Lock from a third thread

  8. Implementing locks: version 4, the code typedef struct __mutex_t { int flag; // 0: mutex is available, 1: mutex is not available int guard; // guard lock to avoid losing wakeups queue_t *q; // queue of waiting threads } mutex_t; • This is very close to real mutex implementations void lock(mutex_t *m) { while (test_and_set(m->guard)) ; //acquire guard lock by spinning if (m->flag == 0) { m->flag = 1; // acquire mutex m->guard = 0; } else { enqueue(m->q, self); m->guard = 0; yield(); } } void unlock(mutex_t *m) { while (test_and_set(m->guard)) ; if (queue_empty(m->q)) // release mutex; no one wants mutex m->flag = 0; else // direct transfer mutex to next thread wakeup(dequeue(m->q)); m->guard = 0; }

  9. Today • Lock (wrap up) • Semaphore • Monitor • A classical synchronization problem: read and write lock

  10. Semaphore Motivation • Problem with lock: mutual exclusion, but no ordering; may want more • E.g. Producer-consumer problem • $ cat 1.txt | sort | uniq | wc • Producer: creates a resource • Consumer: uses a resource • bounded buffer between them • Scheduling order: producer waits if buffer full, consumer waits if buffer empty

  11. Semaphore Definition • A synchronization variable that: • Contains an integer value • Can’t access directly • Must initialize to some value • sem_init(sem_t *s, int pshared, unsigned int value) • Has two operations to manipulate this integer • sem_wait, or down(), P() (comes from Dutch) • sem_post, or up(), V() (comes from Dutch) int sem_wait(sem_t *s) { wait until value of semaphore s is greater than 0 decrement the value of semaphore s by 1 } int sem_post(sem_t *s) { increment the value of semaphore s by 1 if there are 1 or more threads waiting, wake 1 }

  12. Semaphore Uses // initialize to X sem_init(s, 0, X) sem_wait(s); // critical section sem_post(s); • Mutual exclusion • Semaphore as mutex • What should initial value be? • Binary semaphore: X=1 • ( Counting semaphore: X>1 ) • Scheduling order • One thread waits for another • What should initial value be? //thread 0 … // 1st half of computation sem_post(s); // thread 1 sem_wait(s); … //2nd half of computation

  13. Producer-Consumer (Bounded-Buffer) Problem • Bounded buffer: size ‘N’ • Access entry 0… N-1, then “wrap around” to 0 again • Producer process writes data to buffer • Must not write more than ‘N’ items more than consumer “ate” • Consumer process reads data from buffer • Should not try to consume if there is no data 0 1 N-1 Producer Consumer

  14. Solving Producer-Consumer problem • Two semaphores • sem_t full; // # of filled slots • sem_t empty; // # of empty slots • Problem: mutual exclusion? sem_init(&full, 0, 0); sem_init(&empty, 0, N); producer() { sem_wait(empty); … // fill a slot sem_post(full); } consumer() { sem_wait(full); … // empty a slot sem_post(empty); }

  15. Solving Producer-Consumer problem: Final • Three semaphores • sem_t full; // # of filled slots • sem_t empty; // # of empty slots • sem_t mutex; // mutual exclusion sem_init(&full, 0, 0); sem_init(&empty, 0, N); sem_init(&mutex, 0, 1); producer() { sem_wait(empty); sem_wait(&mutex); … // fill a slot sem_post(&mutex); sem_post(full); } consumer() { sem_wait(full); sem_wait(&mutex); … // empty a slot sem_post(&mutex); sem_post(empty); }

  16. How to Implement Semaphores? • Part of your next programming assignment

  17. Today • Lock (wrap up) • Semaphore • Monitor • A classical synchronization problem: read and write lock

  18. Monitors • Background • Concurrent programming meets object-oriented programming • When concurrent programming became a big deal, object-oriented programming too • People started to think about ways to make concurrent programming more structured • Monitor: object with a set of monitor procedures and only one thread may be active (i.e. running one of the monitor procedures) at a time

  19. Schematic view of a Monitor • Can think of a monitor as one big lock for a set of operations/ methods • In other words, a language implementation of mutexes

  20. How to Implement Monitor? Compiler automatically inserts lock and unlock operations upon entry and exit of monitor procedures class account { int balance; public synchronized void deposit() { ++balance; } public synchronized void withdraw() { --balance; } }; lock(m); ++balance; unlock(m); lock(m); --balance; unlock(m);

  21. Condition Variables • Need wait and wakeup as in semaphores • Monitor uses Condition Variables • Conceptually associated with some conditions • Operations on condition variables: • wait(): suspends the calling thread and releases the monitor lock. When it resumes, reacquire the lock. Called with condition is not true • signal(): resumes one thread (if any) waiting in wait(). Called when condition becomes true • broadcast(): resumes all threads waiting in wait()

  22. Monitor with Condition Variables

  23. Subtle Differences between condition variables and semaphores • Semaphores are sticky: they have memory, sem_post() will increment the semaphore, even if no one has called sem_wait() • Condition variables are not: if no one is waiting for a signal(), this signal() is not saved

  24. Producer-Consumer with Monitors monitor ProducerConsumer { int nfull = 0; cond notfull, notempty; producer() { if (nfull == N) wait (notfull); … // fill a slot ++ nfull; signal (notempty); } consumer() { if (nfull == 0) wait (notempty); … // empty a slot -- nfull signal (notfull); } }; • nfull: number of filled buffers • Need to do our own counting for condition variables • notfull and notempty: two condition variables • notfull: not all slots are full • notempty: not all slots are empty

  25. Condition Variable Semantics • Problem: when signal() wakes up a waiting thread, which thread to run inside the monitor, the signaling thread, or the waiting thread? • Hoare semantics: suspends the signaling thread, and immediately transfers control to the woken thread • Difficult to implement in practice • Mesa semantics: signal() moves a single waiting thread from the blocked state to a runnable state, then the signaling thread continues until it exits the monitor • Easy to implement • Problem: race! E.g. before a woken consumer continues, another consumer comes in and grabs the buffer

  26. Fixing the Race in Mesa Monitors monitor ProducerConsumer { int nfull = 0; cond notfull, notempty; producer() { while (nfull == N) wait (notfull); … // fill slot ++ nfull; signal (notempty); } consumer() { while (nfull == 0) wait (notempty); … // empty slot -- nfull signal (notfull); } }; • The fix: when woken, a thread must recheck the condition it was waiting on • Most systems use mesa semantics • E.g. pthread • Thus, you should remember to recheck

  27. Monitor with pthread • C/C++ don’t provide monitors; but we can implement monitors using pthread mutex and condition variable • For producer-consumer problem, need 1 pthread mutex and 2 pthread condition variables (pthread_cond_t) • Manually lock and unlock mutex for monitor procedures • pthread_cond_wait (cv, m): atomically waits on cv and releases m • Why atomically? You figure out class ProducerConsumer { int nfull = 0; pthread_mutex_t m; pthread_cond_t notfull, notempty; public: producer() { pthread_mutex_lock(&m); while (nfull == N) ptherad_cond_wait (&notfull, &m); … // fill slot ++ nfull; pthread_cond_signal (notempty); pthread_mutex_unlock(&m); } … };

  28. Today • Lock (wrap up) • Semaphore • Monitor • A classical synchronization problem: read and write lock

  29. Readers-Writers Problem • Courtois et al 1971 • Models access to a database • A reader is a thread that needs to look at the database but won’t change it. • A writer is a thread that modifies the database • Example: making an airline reservation • When you browse to look at flight schedules the web site is acting as a reader on your behalf • When you reserve a seat, the web site has to write into the database to make the reservation

  30. Solving Readers-Writers w/ Regular Lock sem_t lock; Writer sem_wait (lock); . . . // write shared data . . . sem_post (lock); • Problem: unnecessary synchronization • Only one writer can be active at a time • However, any number of readers can be active simultaneously ! • Solution: • Idea: differentiate lock for read and lock for write Reader sem_wait (lock); . . . // read shared data . . . sem_post(lock);

  31. Readers-Writers Lock int nreader = 0; sem_t lock = 1, write_lock = 1; Writer sem_wait (write_lock); . . . // write shared data . . . sem_post (write_lock); Reader sem_wait (lock); ++ nreader; if (nreader == 1) // first reader sem_wait (write_lock); sem_post (lock); . . . // read shared data . . . sem_wait (lock); -- nreader; if (nreader == 0) // last reader sem_post (write_lock); sem_post (lock); Problem: may starve writer How to fix? Not that straightforward. You figure out

More Related