Presentation is loading. Please wait.

Presentation is loading. Please wait.

Operating Systems NWEN 301 Lecture 6 More Concurrency.

Similar presentations


Presentation on theme: "Operating Systems NWEN 301 Lecture 6 More Concurrency."— Presentation transcript:

1 Operating Systems NWEN 301 Lecture 6 More Concurrency

2 Announcements Homework #1 - Due tonight Tutorial tomorrow 2

3 Recall: Circular bounded buffers data 0123456701234567 producer A nextIn nextOut consumer 3 producer B

4 A Race condition. Imagine this execution sequence. Producer#AProducer#Bvariable++;LDA #0x0111INCASTA #0x0111 4 NWEN301 2015

5 Critical Regions in play 5 NWEN301 2015 Producer A Producer B

6 Simple Prod/Con Use a lock (a binary semaphore -- mutex of 1) boolean produce(int x){ P(mutex); buf[in] = x; in = (in++) % N; V(mutex); return true; } boolean consume(int &x){ P(mutex); x = buf[out]; out = (out++) % N; V(mutex); return true; } 6 What about handling full/empty queues?

7 Semaphore Synch Consider a clever solution to the producer-consumer using counting semaphores to manage the buffers… semaphore mutex = 1, empty =N and full = 0; boolean produce(int x){ P(empty); P(mutex); buf[in] = x; in = (in++) % N; V(mutex); V(full); return true; } boolean consume(int &x){ P(full); P(mutex); x = buf[out]; out = (out++) % N; V(mutex); V(empty); return true; } 7

8 That was tricky! And gets worse with more complex scenarios. The use of 3 separate semaphores suggest the need for better synch primitives: Condition Variables Monitors Critical Regions These constructs lead not only to safer, but also to neater code and fewer bugs but first… 8

9 Pintos, Semaphores struct semaphore { unsigned value; /* Current value. */ struct list waiters; /* List of waiting threads. */ }; void sema_init (struct semaphore *, unsigned value); void sema_down (struct semaphore *); /* Really a P() */ void sema_up (struct semaphore *); /* Really a V() */ bool sema_try_down (struct semaphore *); 9

10 Pintos, Semaphores void sema_init (struct semaphore *sema, unsigned value) { ASSERT (sema != NULL); sema->value = value; list_init (&sema->waiters); /* Initialise a list/queue for waiting threads */ } 10

11 Pintos, Semaphores P() void sema_down (struct semaphore *sema) { /* Really P() */ enum intr_level old_level; ASSERT (sema != NULL); ASSERT (!intr_context ()); old_level = intr_disable (); while (sema->value == 0){ list_push_back (&sema->waiters, &thread_current ()->elem); thread_block (); } sema->value--; intr_set_level (old_level); } 11

12 Pintos, Semaphores V() void sema_up (struct semaphore *sema) {/* Really V */ enum intr_level old_level; ASSERT (sema != NULL); old_level = intr_disable (); if (!list_empty (&sema->waiters)) thread_unblock (list_entry (list_pop_front (&sema->waiters), struct thread, elem)); sema->value++; intr_set_level (old_level); } 12

13 Pintos, Semaphores bool sema_try_down (struct semaphore *sema) { enum intr_level old_level; bool success; ASSERT (sema != NULL); old_level = intr_disable (); if (sema->value > 0) { sema->value--; success = true; } else success = false; intr_set_level (old_level); return success; } 13

14 Condition Variables Are a higher level synchronization primitive. It makes coding of synchronization: more structured, easier to follow, and debug. It does not enforce mutual-exclusion, it needs a lock or semaphore as a mutex. 14

15 Condition Variables The general problem is the need to wait for some condition to become true within a critical section. For the producer the condition is that the buffer is not full. For the consumer the condition is that the buffer is not empty. For this we use condition variables. 15

16 Condition Variables Used to wait for a condition to be true Essentially just a container for waiting threads Functions: Wait(condition, lock): release the lock, put thread to sleep until condition signaled. Signal(condition, lock): wake a thread waiting for the condition. Caller must hold the lock. Broadcast(consition, lock): same as signal, but wakes all threads. 16

17 struct lock *mutex; // this is a class struct condition *notFull, *notEmpty;// Condition var struct buffer *buf; // buffer } Shared Prod/Con with CV bool producer(int x){ P(mutex); while(isFull(buf)) cond_wait(notFull, mutex); put(buf, x); cond_signal(notEmpty, mutex); V(mutex); return true; } bool consumer(int &x){ P(mutex); while(isEmpty(buf)) cond_wait(notEmpty, mutex); x = get(buf); cond_signal(notFull, mutex); V(mutex); return true; } 17

18 Monitors A monitor is like an object, with all state (data) private and a set of methods. With the addition of synchronisation, where invoking any method results in mutual exclusion over the entire object. You can also use monitors to control access to data not encapsulated in the monitor (acting as a gatekeeper). 18

19 Monitors Think of it as a thread-safe object. Multiple threads can use the object. However, at any one time only one thread may be executing any of its methods. The object has a set of methods and CVs. CVs allow threads to give up exclusive access while they wait for a condition. 19

20 The Monitor Shared Data Methods Constructor CV buf[] Waiting Threads 20

21 CVs in Monitors Essentially the monitor provides mutual exclusion. In addition we need additional constructs to allow us to wait on conditions (like CVs). Condition x;// Condition is a type x->wait(); // suspend process x->signal();// wake exactly one process 21

22 Monitor PC example Monitor ProducerConsumer{ public: ProducerConsumer(); bool produce(int x); bool consume(int *x); private: Buffer *buf; Condition notFull, notEmpty; }; Monitor:: produce(int x){ while(buffer->isFull()) notFull.wait(); buffer->add(x); notEmpty.signal(); } Monitor::consume(int *x){ while(buffer->isEmpty()) notEmpty.wait(); *x = buffer->remove(); notFull.signal(); } 22

23 Monitor use ProducerConsumer pc = new ProducerConsumer();// shared … // producer thread// consumer threadwhile(true){ pc.produce(rand()); pc.consume();} 23

24 CVs in Monitors What happens when a signal is issued? The unblocked process will be placed on the ready queue and resume from the statement following the wait. This may violate mutual exclusion with both the signaler and signaled process executing in the monitor. 24

25 Monitor CV Semantics We have two choices: The signaling process must wait until the signaled process has left the monitor, or waits on another condition. The signaled process must wait for the signaler to leave the monitor or wait on another condition. 25

26 A Pintos CV Implementation struct condition { struct lock *lock; /* A lock with which this condition variable is associated */ struct semaphore s; /* The semaphore which I will use for storage */ int wait_count; }; void cond_init (struct condition *cond){ ASSERT (cond != NULL); cond->lock = NULL;/* lock associated with this thread */ cond->wait_count = 0; sema_init(&cond->s, 0); } 26

27 A Pintos CV Implementation Note, also wrapped the pintos functions: void P (struct semaphore *sema) { sema_down(sema); } void V (struct semaphore *sema) { sema_up(sema); } 27

28 A Pintos CV Implementation void cond_wait (struct condition *cond, struct lock *lock) { /* Kris didn't like the original impl, so he rewrote it (except for the asserts) */ /* if no lock associated, associate the one passed in, this should only happen when the cond is empty */ if(!cond->lock) cond->lock = lock; lock_release (lock); cond->wait_count++; /* Gotta do this before P(), otherwise it will hang */ P(&cond->s); lock_acquire (lock); } 28

29 A Pintos CV Implementation void cond_signal (struct condition *cond, struct lock *lock UNUSED) { ASSERT (lock_held_by_current_thread (cond->lock)); /* If there is something waiting, signal it - otherwise no need */ if(cond->wait_count){ cond->wait_count--; /* Gotta do this before V(), otherwise can be interupted with horrible results */ V(&cond->s); if(!cond->wait_count){ /* The CV is now empty, we can disassociate the lock */ cond->lock = NULL; } } } 29


Download ppt "Operating Systems NWEN 301 Lecture 6 More Concurrency."

Similar presentations


Ads by Google