Lecture 3, Unit 1: Introduction to condition variables and monitors

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
welcome to the third series of online lectures this one is on monitors and condition variables and the first part will be talking about the basics of what a monitor is what a condition variable is and you will learn how to use condition variables in C so let's start out by understanding the motivation for having a new synchronization contract beyond semaphores well one thing you may have learned both from watching the videos and from practicing in class that semaphores can be hard to use particularly if you have complex resource patterns it's very difficult to capture the relationship with only semaphores for example in the sleeping barber case you couldn't use the semaphore to keep track of how many available chairs there were what this means is that you often need extra state variables to record additional information besides just the simple counter of the semaphore and this means you need to use extra semaphores we found that we often needed one for mutual exclusion for the state variables and then another semaphore for each class of thread that needs to do some weighting the result is that some of words can be difficult to use correctly and as a result they can lead to buggy code and they can lead to it being harder than necessary to write code to solve this problem a long time ago people invented a new programming construct called a monitor originally a monitor was designed to be built into a programming language and not just some function to call through a library the idea with the monitor is that it supports controlled access to shared data controlled access means that the monitor keeps track of who is allowed to access the shared data and when and the shared data means this is all about shared data access by multiple threads to immolate monitors a programmer would use a monitor and the compiler would then add synchronization code automatically for example acquiring and releasing locks and at runtime those locks would then enforce the synchronization rules so a question is why does this help and the reason is that it takes the effort of synchronizing your code away from the programmer and gives it to the compiler and the runtime this means that it's less work for you it's also more likely to be correct because there's fewer opportunities to make bugs fundamentally a monitor is kind of a software module like a class in Java that encapsulates first some shared data structures so when you have shared data you would put it together inside a monitor second a set of procedures that operate on that shared data similar to method functions in Java and finally synchronization rolls between concurrent threads or processes that invoke those procedures so this is how you can control when a thread should execute when it should not execute the major benefit of a monitor is to protect the data from unstructured access meaning you cannot access the data without following the synchronization rules this is like a very similar to a private member variable inside an object-oriented language this means if you can only access it through the right procedures then you can only access it in the right way and you won't have as many synchronization problems here's a picture of what a monitor looks like the monitor itself is the blue box and inside there are operations shown as rectangles and there's some shared data which is the data operated on by those procedures I need to note here that monitors were invented at a time before there were threads and people looked at them in the kernel of different processes so much of the terminology for monitors uses processes and threads interchangeably so at most one of the rules of the monitor is that a monitor provides mutual exclusion between all the procedures for the shared data this means that only one thread or process can be executing it at a time if other threads or processes want to access it they have to enter a queue and wait to enter the monitor the major facility of a monitor is mutual exclusion as you just saw meaning that only one thread can be executing at a time thus synchronization is implicitly associated with a monitor this means every time a thread enters a monitor procedure the monitor runtime will acquire a lock that prevents other monitor procedures and that same one from being called thus if a second thread tries to enter a monitor procedure or a different one that thread blocks until the first has left the monitor note that this is more restrictive than sumit force because semaphore is made no restriction and when threads could execute but it's easier to use most of the time because most of the time this is what we want we want mutual exclusion over some data notice also that here we are explicitly associating synchronization with data previously the programmer had to remember which semaphore to use with which data here the monitor has both the data and the mutual exclusion rules built into it one thing that happens that was once a thread is inside a monitor it may discover it can continue or it might wish to sleep suppose the thread is waiting for something to happen consider for example a bank account acquire thread is waiting for the balance that the above a certain value before continues in that case the thread may wish to wait until some thread comes in and makes a condition true another example would be a bounded buffer where a consumer thread may need to wait until a producer thread produces some data monitor is used something called a condition variable as part of the monitor that allows a thread to wade into B signals condition variables have two operations wait which waits for condition variable to be signaled and signal which signals a condition variable and can wake up other threads one thing to note is that condition variables should only be accessed from inside the monitor in a language where the monitors are built-in this can be enforced another thing about using monitors is something called a monitor invariant this is basically the consistency rules for the data so typically any data structure has some rules on what it means to be a correct data structure in the bounded Bob for example it means that the count of items should be between 0 and the size of the buffer if the kind of items is less than 0 or greater than the size of the buffer something is wrong in a doubly-linked list you might want to say that the list is completely connected in both forwards and backwards directions and a priority queue you might want to say that for all the items in the list the priority of an item must be less than the priority of it for equal to the priority of its successor the role of monitors is that this inherent variant must hold whenever the monitor lock is free whenever no thread is inside the monitor while it's held you can violate these invariants many flip the data structures for example while inserting into a linked list or priority queue by the time the thread releases the lock though it must make sure the invariant holds this means that any thread entering the monitor can always assume at the top of a procedure that the invariant holds such as the linked list is correctly formed now as I said monitors were originally constructed to be part of a language originally was called concurrent Pascal and were adopted as part of Java if you want to use a monitor in Java use the synchronized keyword on a method so here's a counter we have a private integer if you want to have a procedure that guarantees mutual exclusion you can put the keyword synchronized in front of the procedure your increment this means that the compiler will automatically acquire a lock on the object being used on entry to the function increment and release the lock automatically when it leaves this is nice because you don't have to the code you don't have to remember that this is shared state and you have to acquire a lock and you don't have to remember to release it and see things aren't so good you have to add the locks yourself because monitors are not part of the C language instead monitors are typically implemented as a library they're provided by the pthread library in this case there are functions in library to use the basic mutual exclusion of monitors you can just use pthread mutex locks here's the same example how you would code it in C you would have a data structure and you would put a lock inside the data structure explicitly and then in your procedures that manipulate the data structure you would add code to acquire the lock at the beginning and release the lock at the end so the effect is the same as in Java the difference is you have to write the code yourself and let it set up letting the compiler do it for you condition variables are what make monitors interesting I said before that there were signal and weight operations on monitors the thing that you signal or weight on is called a condition variable in this way it's very similar to a semaphore which also has a signal and weight operation what's important to note is that the weight condition variable signaling weight works is very different than the way semaphores work the major difference is that condition variables don't have any history remember with semaphores when you signal a monitor the value gets incremented meaning that a subsequent call to wait may not wait because the semaphore remembers the value of the counter in a condition variable there is no history associated condition variable if somebody calls signal one thread call signal another thread calls wait later that later thread has no way of knowing that signals called previously we'll see how this works in practice in a little while there are three operations that can be used on condition variables the first operation is weight so when you wait in a condition variable the weight operation will release the monitor lock so somebody else can get in this is important because remember the reason to wait is you're waiting for something to become true and if you're holding the lock nobody can change the state variables they are waiting for to change so when you call weight the weight operation will release the lock and it will add the thread to a queue saying that it's waiting for a signal this means that condition variables implicitly are really just queues they don't actually have any state of their own the second operation is called signal or sometimes notify this means that when a thread call signal it will wake up one that is waiting on the condition variable if any if no threads are waiting signal does nothing at all while calling signal the signaling thread keeps the monitor lock and keeps running on the CPU it does not immediately switch to the waiting thread instead the waiting thread is made ready to run meaning that it is taken off the queue but the waiter now has to wait to get the monitor lock so the waiter will run when the signaler leaves the monitor this means that when the signal actually starts running the condition it was waiting for may not become true and again we'll see what this means in a little bit one thing to know here also is that wait releases the monitor lock meaning that the monitor and variant must be true before calling fate signal does not release the monitor lock so that the monitor environment does not need to be true when calling signal the final operation is broadcast also called notify all this will wake up all the threads that are waiting on a condition variable this can be nice if you want to for example tell all the threads in the system that you're done with something they're waiting or if you have threads waiting in different things you can wake them all at once and let them figure out whether they're done or not again we'll see how this meat works in a little while let's talk more about how to sit how to correctly wait on things when a thread waits on a condition variable it releases the monitor lock and puts their weight rating thread on the condition variables cue the one important thing that it guarantees is that no other thread enters the monitor before the thread is on the queue this means that nobody will call signal between the time when it decides the weight and actually gets put on the cue threads will then stay on the queue until they're signaled if they're the head of the queue or there's a broadcast which wakes up all the threads in the queue after a threat has been signaled that any thread that was woken up has to now wait to get the monitor locked before returning so it doesn't immediately get to go inside the monitor and start running again and said it has to wait for the lock an important thing to notice is there might be other threads already waiting on the lock that this thread queues behind so here is a general use of how to use weight and signal this is a counter example so we have a counter structure it has a lock which is the monitor lock in a condition variable P thread find be above zero this condition variable represents the condition of is the counter above zero we have three operations on the counter the first is increment the increment code will acquire the lock it will then increment the counter if the counter value is above zero it will call pthread cond signal on the above zero condition variable and then release the law the ID here is if any thread is waiting for the value to be above zero it will get signaled when this happens the decrement counter will again acquire the lock decrement the counter and release the lock in this case it does not signal the value because the value will never go from not being above zero to being above zero and decrementing the final function is a print gal function that waits for the counter value to be above zero and then prints out the value notice in the middle of this function that if the value is less than or equal to zero the print bail function will call pthread cond wait for it to become above zero the idea here is that was condition variables you test the condition using an if statement basically on some predicate you want to become true over your shared state if it's not true you call pthread cond wait and then when it returns hopefully that condition is true another thing to note is that with the library version of condition variables you need to pass in the lock to the condition variable weight function also and the reason is that when you wait on a condition variable you have to release the lock however this is action incorrect use of signal and the reason is as I said that the condition you're waiting for in this case that the count is not less than or equal to zero may not be true anymore consider for example the schedule of threads shown in the lower right the counter starts up at 9 is 1 the first thread 3 t1 will run and call increment and raise the count to 0 suppose thread 2 comes along and calls print Val in this case the count is 0 so it waits thread 3 may come along and call increment which will raise the count to 1 and it will call signal however thread 1 may come along and try to get into the monitor before thread 2 finishes waking up and gets into the monitor in this case thread 1 may come along and decrement the counter back to 0 so in thread 2 wakes up the count will be 0 and will print out the wrong value the problem here is that the print Val function did not check whether the condition C arrow count is less than equal to 0 was true or false after I woke up from the wait this is a general problem or issue with using condition variable and signals which is that whatever condition you're waiting for to become true may not actually become true this means that a signal is really in some ways a hint that something may have happened not a guarantee that it did happen the major reason is that another thread may have entered the monitor between the call to signal and when the thread actually wakes up the implication of this is that when using the condition variable to wait for something you have to check your conditional predicate inside a loop typically a while loop the box on the right shows the correct code for using condition variables to wait on a counter instead of using if CRO count is less than equal to zero it says while CRO count is less than equal to zero pthread cond wait this means that if the thread wakes up it will retest is the counter above zero if it is not above zero it'll go back to sleep if it is above zero it keeps going now most of the time the condition you're waiting for will have happened and you don't need the while statement however it may not happen and in those cases to make your code be correct in all cases you need to use the while statement this is the end of the first part of the lecture on condition variables monitors please take the first quiz and condition variables before watching the next lecture
Info
Channel: Mike Swift
Views: 51,143
Rating: 4.6872964 out of 5
Keywords:
Id: xCNhhwVQ4Y8
Channel Id: undefined
Length: 14min 31sec (871 seconds)
Published: Sat Apr 20 2013
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.