 Okay, welcome back to 162 everybody. We are going to do the third lecture we have on scheduling today. And I definitely encourage you to catch up on the other lectures if you've gotten behind since the midterm. The one of the things we did talk about last time, which I wanted to remind you of was real time scheduling. And normally when you hear about scheduling in an operating systems class. You often just hear about sort of the standard performance sensitive or latency sensitive. You know responsiveness and fairness sensitive scheduling algorithms but I always like to talk a little bit about real time because real time is different in that predictability is important. So rather than what you typically worry about in scheduling here it's far more important to be predictable than even to be fast. Okay, because you want to predict with confidence the worst case response time, and in real time scheduling performance guarantees are often given per each task you're sort of guaranteed a given deadline will be met. The way you get that guarantee is you have to give the scheduler information about what your worst case scheduling time worst case computation time might be. And it could conventional systems we talk about performance, and you know throughputs important. Okay, and so real time is really about enforcing predictability and it's important because for instance, talking about things like hard real time might show up if you're worried about physical scenarios how long between when you press the brake on a car and when the brakes actually engage that might be a real time problem. And it's very important that you meet a deadline there because if you don't then the user might crash. Now there is a discussion here about GPU scheduling we probably won't talk about that we're mostly talking about regular CPU scheduling for now. The thing about hard real time scheduling again is it's really important to meet the deadline. And this can be a situation where if you don't meet deadlines, maybe the car crashes, or you have a system that's in a hospital and maybe the patient dies if the real time scheduling is not met. And we even introduced something called earliest deadline for scheduling last time, which is a very common one for doing real time scheduling. We also sort of distinguished between hard and soft real time. The key thing about hard real time is it's crucial that you actually meet the deadlines and you assume that you don't want to miss any deadlines where soft real time is a situation where you want to meet deadlines with high probability, and typically might be in something like multimedia servers or whatever. And there's something that like the constant bandwidth server CBS, which we didn't talk about last time is a variant of earliest deadline first for multimedia. All right. The other thing that we was talking about that I wanted to mention was stride scheduling stride scheduling is something that we talked about after we talked about lottery scheduling. And this was the notion of achieving a proportional share of scheduling without resorting to the type of randomness we talked about in the lottery scheduling. And thereby sort of overcoming the law of small numbers problem where lottery scheduling really only comes out when you have long enough tasks that you can can meet that law of small numbers, law of large numbers basically stabilizes it. The stride of each job you could think of is something like you have a large number divided by the number of tickets. And, for instance, W might be 10,000 and perhaps task a has 100 tickets B has 50 C has 250. And those instances, basically, the strides are for instance 100 for a 200 for B and 40 for C. And what is that stride we talked briefly about the fact that sort of every time you get to schedule and run for your time slice, you you add your stride to your counter and those tasks with the smallest accumulated ones that get to run. And so as you can imagine the low stride jobs with lots of tickets running more often. And this is starting to get a way of applying fair queuing to scheduling, and, and basically thereby giving a proportional fraction of the CPU. Okay, and really what I talked about a little bit too quickly at the end of lecture because we ran out of time and I wanted to repeat here for everybody was this notion of the Linux completely fair scheduler or CFS. And this is actually in use you're probably using it if you have a Linux box. And the goal here is that each process gets an equal share of the CPU. So rather than talking about priority scheduling or talking about round robin scheduling or some of the other ones we were talking about which don't tie the schedule directly to the CPU. CFS like stride scheduling ties the amount of execution time you get to the CPU. And so as a simple example, we'll get to more complicated ones in a second here. The idea here that end threads are running simultaneously, you have this model as if the CPU were subdivided into end pieces, and somehow we were able to get in pieces of the CPU to each of the end threads. And if you could somehow do that then the threads would run at exactly one over nth of the time and they'd all get an equal fraction of the CPU and everybody would be happy. Okay, and so the model is something like simultaneous multi threading or hyper threading where each thread gets one over end of the cycles. Of course in general you can't do this. Yeah, hyper threading maybe lets you do that a little bit with one or two threads but certainly not a big end. And so what we need to do is figure out how to approximate this idea that every thread gets one over nth of the CPU. But without having that ability to really subdivide those cycles. And so of course the operating system gives out full speed cycles. And so we have to use something other than the some way of keeping the threads in sync so they sort of get on average one over n. And that's really the basic idea here which is we're going to track CPU time per thread and schedule the threads to match up an average rate of execution. So you could look at this I mean in this previous figure what I had here was one over n of the cycles are given to each thread and so they all kind of progress at the same time. Okay, in this newer idea here, the threads of course when they're running are running fast they're faster than one over nth of the speed, but we don't run them all the time and if we when we stop we can take a look at thread one two and three, and notice to make the decision here that thread two is behind in its average amount of cycles. And so we'll choose to schedule that one next. And so it's, we've sort of keep the heads of the threads running at the same speed on average. So we choose the thread here with the minimum CPU time total. And this is very closely related to fair queuing as a general idea if you're familiar with that from networking. And if we do that so what we're just to be clear what we're doing is whenever the thread gets to run, we're counting its total cycles, and then when we stop. We put it back in the scheduling heap and then we pick the thread that has the the least number of cycles so far and we keep doing this in a way so that on average we get the same rate of execution between all of the threads. Okay, and you could imagine if you know if you remember your 61 be ideas is that we probably want to heap like scheduling queue so we just put the threads in the heap, and then the one at the top is the lowest has the lowest number of total cycles and it's the one that we schedule for next. All right, questions. So this is where we're going after rate of execution here rather than those other metrics that we were going at before like, you know, letting it just run for a little while and then switching it out after time expires. And I'll show you in a moment, how we can now use this to give us something like priorities but in a way that still maintains this notion of rate of execution rather than strict priorities. Okay, so sleeping threads, of course don't advance their CPU time so what's interesting about this is that when they wake up and they're ready to run their way behind and so they get selected to execute. And as a result, we get this interactivity idea automatically. Think of this in contrast to the 01 scheduler. And does CFS has any concept of priority yes just give me a second I'll get to that. But if you remember the 01 scheduler. The idea was we had some really complicated heuristics that would adjust priorities based on how much interactivity we thought was short the first time seemed to be to try to make sure that things that had really short burst times and might be likely to be interactive tasks would get higher priority and get to run as soon as they became runnable. Here we get this automatically just because we're trying to give the same rate to everybody. And if a threat is sleeping it's not achieving its rate so when it wakes up suddenly it's got the CPU. And this is beginnings of why this was so appealing, and why basically Linus and others completely throughout the 01 scheduler for CFS because 01 had gotten way too complicated. So, so CFS have some nice properties to it but we still want to worry about a few things we talked about for instance starvation last time and responsiveness. So, in addition to trying to be fair about the rate of execution we certainly want low response time to make sure that no thread left behind right and so starvation freedom might be another way to look at that. And so we want to make sure everybody gets to run at least a little bit. And if you recall when we were talking about multi level queuing, there was this worry that the queue, the thread sitting at the very bottom there and the lowest level queue might never get to run. So what CFS does is it actually make sure that everybody gets to run a little bit and so it has something called the target latency, which is a period of time over which every process, it gets to run a little bit. Okay, and so call the quanta target latency over and in this case that means that we make sure that every thread runs one over into the time, and that makes sure from a time standpoint we still have the ability to, you know, be sure we're going to run. Now, so far it sounds like we're moving our way back into round robin but just hold off as soon as we get to priorities you'll see how this is fundamentally different. For instance, a target latency of 20 milliseconds is not out of the question for CFS. If you got four processes running then each process gets filled five milliseconds time slice. Okay, and the problem that you might think here is if we have a 20 millisecond target latency but 200 processes, then this all falls apart. And so CFS does have some outs. We call it a way to get by this high overhead case, right, and that's going to be that we're going to have a minimum quanta time we never want our overhead to get so high that for instance 0.1 milliseconds is essentially what I told you a contact switch time can be in some circumstances so it would be really bad if we switched every contact switch time. Okay, and so that's basically a throughput metric. And so CFS has something called minimum granularity, which is the minimum length of any time slice. So the target latency, 20 milliseconds minimum granularity is one millisecond that says in this case of 200 processes. We basically don't run anybody any shorter than a millisecond. And so when you're, when you have so many things running that you hit the minimum granularity that's typically when the properties of CSS CFS start to fall apart a little okay. But just so you know, there is this minimum granularity piece as well. Okay, priorities. Now, as those of you have used Linux recently it still has priorities. I wanted to tell you about what priority and unix typically is and that's the nice value. The industrial operating systems in the 60s and 70s gave you an actual priority that you could set directly. When Berkeley Unix was kind of working on priority they decided to call this nice instead niceness. Okay, and so when we were talking about the 01 scheduler we mentioned the fact that there were 40 priorities for the user. They were actually called nice values, and they range from minus 20 to 19 there's 40 of them in there, and negative values are not nice. Positive values are nice, and something that's more negative than another one gets higher priority. Okay, so, even if you were to look at priority 19 versus 18 the thing with the nice value of 18 is running with a slightly higher priority. And so for instance what you would do is you could start a job and then you could run nice on it. And so if you wanted to let your friends get a little more time you might do nice on your job and that would raise the niceness value and, however, only the root user is allowed to lower nice. The regular users are allowed to raise the nice values. So, as I mentioned, the scheduler puts higher nice values or lower priority to sleep more. And in the 01 scheduler this actually translated fairly directly to priority if you remember. I showed you that there were 140 total priorities in the 01 scheduler that the, the highest hundred of them were for what was called real time scheduling. Okay, and the, the lower 40 were for these nice values. But how does this translate to CFS so CFS was a drop in replacement for the 01 scheduler. So clearly there was some notion of niceness that must have been there and priority is certainly useful because certain certain things are higher priority than others. But this idea that CFS is a fair queuing type scheduler says that there must be something a little different here. And because this is not strict priority. And so the idea here is that you're going to change the rate of execution based on priority, you're not going to say that higher priority always runs over lower priority but instead higher priority has a higher rate of execution than lower priority. Okay. So how does this work. So, CFS as I shown you so far, isn't really all that different from round robin okay because I kind of said, you know you get one over into the CPU you get a quantum that's one over and of the target latency and so it sounds like I just renamed round robin but in fact I didn't. Okay, what I did was, I only showed you the uninteresting case where everybody has the same priority. We have more CPU to some and less than others, what we're going to do is change the rate. Okay, and so we're going to use weights for that. So what I showed you earlier was one in which the basic quanta was everybody got target latency over and and that was the basic equal shares. A weighted share is something where every thread has a weight. And then what we do is we take the current weight divided by the sum of all the weights to find out what fraction of the total weights a thread has times target latency and that tells me my quanta. Okay, so now I'm adjusting the time that I'm allowed to run based on based to some extent on target latency. And we're going to reuse the nice values to reflect the share rather than the priority. So CFS uses nice values to scale the weights, but it does so exponentially. Okay, now this looks messy but it's not bad so just hear me out. So the idea is that the weight is 1024 divided by 1.25 to the nice value so what does this say this says that positive nice values have lower weights the negative nice values. Okay, so a high weight. Basically, he's going to be something that has a, so as you see here so high nice value has a low weight and low nice value as a highway. Okay, and so to CPU tasks separated by nice value of five which you find is the one with the higher nice value has three times the weight of the one with the higher. And that's doesn't matter where it is so if you have 19 versus 14 or zero versus minus five, you're still going to get the same proportional difference there. Okay. And now we're going to use virtual runtime instead of CPU time. Okay, and the virtual runtime. Okay, so the number 1024 because they did the thing to realize here is it doesn't matter what the number 1024 is we could put any number we want here because the only way we use it is with the same number in the numerator and denominator. Okay. And so, yeah, this is more about wanting integers than it is about anything else. 1024 is end up canceling out in this weighted share number so the actual number is more of a convenience for the number of bits you have in your weight than anything else. All right. Now, so just to give some of these numbers to you just for the heck of it if you have a target latency of 20 milliseconds minimum granularity of a millisecond. And you have two CPU bound threads which are always running then a might have a weight of one and be might have a weight of four. How does that work well with the target wet latency of 20 milliseconds. Then these two weights which would come from that exponential factor I gave you earlier means that the time slice for a might be four and for B might be 16 so notice B as a bigger time slice than a. Okay, and they're in the ratio one to four. Wow. So, let's go back now to the fair queuing aspect. Okay, so fair queuing. How do we fit the rate of execution back into the picture here because so far we're talking about time of execution but that isn't rate. So to fit the rate of execution back in the picture. What we want is we want somehow to give a slightly faster CPU to things with higher weight. Okay, and so if you look here. Here's an example where we want to give say more time to the higher weight than the lower weight one. But to do our, our CFS scheduling, what we're going to do is we want to schedule this in a way that we can put everybody in the same heap no matter what their time is and always pick the one that has the lowest amount of time so far. And so we do is we schedule virtual time instead of real time so this is kind of cool so listen to me for a sec here. So what you do is for higher weight, the virtual runtime increases more slowly and for lower weight increases more quickly. I've got a higher weight. I let the CPU run for a second, but I only will look at say a quarter of a second worth of virtual time, whereas for the lower weight one here if I run for a second I will register a second of virtual time. If I put those together into my virtual CPU and I make sure that virtual time always advances at the same rate. Then voila. Now the ones with the higher weights get to run more time than the ones with the lower rates and it does it in this very simple scheduling idea here where I just want to make sure every thread has the same virtual CPU time. Okay, so schedulers decisions are based on virtual CPU time. It turns out you take the amount of time you just ran when you give up the CPU, you divide it by your weight, and you register that virtual time and then you put yourself back in the heap. And it turns out they use a red black tree to do this, which is a convenient heap that I'm sure you've learned about. It basically you can always find the next thread to run in 01 because it's at the top of the heap. And then you run it for a while and you do this same trick. And now by the way the question here that's in the chat is does this assume that every process has only one thread so this scheduling decision is based per thread not per process right now. Okay, and if you wanted to have some tie to the processes, then what you would do is you would adjust their total weights to reflect that. Okay, so you can you can basically scale their weights to do some process based scheduling if that's what you were desiring. All right, now that in contrast to the 01 scheduler which every task that the 01 scheduler was doing was independent of the number of threads, because we're using a heap the scheduling time here is order log in but log in isn't too bad. That result though is this incredibly simple schedule. Okay, so notice that priorities are reflected by a greater fraction of the CPU cycles or a greater rate of execution. That thing about interactivity just happens because when you go to sleep and you wake up your virtual time is behind and so you get to run right away. And so all of the really complex heuristics that were in the 01 scheduler have been replaced by this very simple idea of scheduling virtual time. All right, any questions. So by the way, if a thread schedules spawns too many or if a process spawns too many threads then then the operating system can make a decision about whether to shut them down or not. Questions. So this is a fair queuing with execution rate mechanism of scheduling. So, just to close out this scheduling idea so I wanted to go through the CFS and a little bit more detail because this is an actual scheduler that you're probably using now that works pretty well. It's not a real time scheduler because it's working with rates of execution. So if you want a real time scheduler, you could install, for instance, you know earliest deadline first scheduler on Linux and you could use that. So is there a cap on how much interactivity boost that a long running thread can have yes. So if you get too far behind and there is there is a little bit of a reset that goes on there but for short for short shutdowns it doesn't happen that way. Okay. Now, if you care about CPU throughput you might use first come first serve because that's the one that uses the things in the most efficient way. If you care about average response time then you might want some approximation to SRTF because remember SRTF is optimal IO throughput. Excuse me, you might use an SRTF approximation. Fairness, well you might use CFS or if you're caring about the way time to get to a CPU perhaps you'd use round robin. If you're interested in meeting deadlines you probably use EDF. One thing we didn't talk about in this class is rate monotonic scheduling which is a type of scheduling that's not as optimal as EDF but you can actually do rate monotonic scheduling with a strict priority scheduler like we talked about is the top 100 priorities in Linux and so you might do that instead of EDF. If you're interested in favoring important tasks you might use a strict priority scheduler. Okay. All right. So a final word on scheduling before we move on to another topic is when do the details of the scheduling policy and fairness really matter when there are enough resources to go around so everything we've been talking about for scheduling is all about how do you choose to divide up your resources among a bunch of shared threads. If you didn't care about resources you wouldn't have to schedule or if you didn't care about you know if you didn't have more than if you had one thread for instance that's what I meant to say you wouldn't have to schedule. Okay so when there aren't enough resources to go around your scheduling policy might start to get really important. Okay. That's when you really have to be careful about your scheduler. Okay. When should you just buy a faster computer. So it could be the case that your resources are so scarce and you have so many things you have to run that your computer is just not fast enough. And you know this goes with pretty much everything. When might you need another network link or expand your highway or any, any number of questions around the rates of restricted resources are all about how do you schedule and then if scheduling starts to fail when do you buy bigger faster, larger things. Okay. So the one approach is you buy it when it's going to pay for itself and improve improved response time. Perhaps you're paying for the for worse response time and reduce productivity customers being unhappy etc. You might think you should buy a faster something when something's utilized 100% because then you know you you can't utilize it anymore. But I want to tell you that running anything at 100% is always bad. You as an engineer should know that it never want to run anything at 100% if there's any randomness in the system at all. And the reason is that you start getting this queuing behavior like I've shown you in the curve here. Now we're going to talk about queuing theory and more depth in a little in a few weeks. I'm actually we may talk a little bit about it next week but in general you see a curve that looks like this with utilization on the x-axis, something like response time on the y-axis and a nonlinear curve that starts out with a linear section in the low part but then rapidly starts rising and when you're looking at the regular models that aren't realistic but totally mathematical this high end near 100% goes to infinity. Of course we know nothing goes to infinity in real life but it always goes pretty high. And so 100% is definitely not the time at which you want to buy something because you're already seeing this huge super linear increase in response time. And so 100% of the customers have already left you. Right, so an interesting application will tell you where the curve comes from in another lecture but here one thing might be to say what as long as I'm in the linear portion of utilization, things are basically okay. The moment I start getting to the point where it's super linear and things are going up faster than they were in the linear section that's when I start to worry about my resources so right around the knee of the curve is usually a good place to consider buying something new. Okay. And just to give you another instance of 100% being a bad idea if you know that a bridge can handle some maximum weight say call it you know 200 tons. You do not want to be running at 100% on that bridge because you know that any sort of randomness is going to take you over the edge right you want to be running down in the linear place where the bridge is behaving normally. Okay. Questions. We'll tell you why this curve is super linear in a couple of lectures. Okay. So I actually had this is still grading when I did these slides earlier today but I believe the grading is pretty close to done. So, we'll get those out to you there's also, I know people have been waiting for the bins those bins are out. There's going to be a little bit those bins represent final total points as they do in other classes but for midterms there is a an offset that you can use with the midterm from historical data that will let you kind of how you did on the midterm. And we'll explain that later in a post. But I will say that having we graded this and this midterm was clearly too long and I apologize for that it was definitely definitely hard, harder than I think we were expecting so I guess we'll figure that out for the next one. So my apologies there. The other thing is group evaluations. Oh, and just to cap this off I believe you'll be seeing the release of grade scope grades in either tonight or maybe even early tomorrow but very soon. And that'll be the process, then you can start putting in grade, regrade requests and so on. So the evaluations are coming up for project one. In fact, they may have been mailed out today or they will be tomorrow at the latest. Every person in the evaluations are going to get 20 points per other partner, which you can hand out as you wish. No points to yourself. Okay, every term I have to say no points to yourself. So this is not about saying well I've got four people in my group, 20 times four is 80 I'm going to give 80 points to myself okay that doesn't work that way. The other part is, you have three other partners, three times 20 is 60 and you can hand those 60 points out anyway to your other partners, they're going to evaluate you. Okay. And the reason we do this, and by the way your TAs are going to moderate what's, what's being said here this is just one piece of information that we use to figure out how things are going. But in principle, projects are a zero sum game and you have to participate in your group. Okay, and there are some of you that seem to have fallen off the earth and aren't responding to email. If you really don't participate at all. And we have that documented in various ways then it's possible that some of your points may end up going to your partners instead of to you. So, this doesn't happen often but it's a way for us to really reward project members that are working and have non working team members. Okay, so please try to try to work. Make sure that if there are any group dynamic issues that your TA knows. And I think I offered that I'd be more than happy to sit down with groups to talk about ways of collaborating if that helps. But make sure your TA knows any issues that you might be having with your group. And let's see if we can make projects two and three even better. So, are the point distributions per person anonymous so the point distributions are in fact not handed out at all so those are purely for our information. They, none of your team members know how you graded them and they don't. And you don't know how they graded you, but you're going to talk to your TAs and their TAs will have a good idea how you're doing as well. The, the other thing just to say about this is, you know, if you were 100% happy with your group members, you could give your other three partners or whatever 20 points each and that would be an example of a fully a very happy person with the rest of their group. Okay. The other thing I mentioned was this notion of a group coffee hour. Look for opportunities. I think in maybe the same email that we send out either group evaluations or our, how are we doing a third of the way through the term survey. We're going to tell you how to basically give it get maybe get extra points for screenshots of you with your other team members on zoom. You know, a thumbs up or beverage of choice or whatever we're going to call these group coffee hours. Okay. And don't forget turn cameras on for discussion sessions, if at all possible. All right. That was a long administration of you. I realize it's really rough being in a fully virtual term. And you know a third of the way through the term this is the point at which things start getting a seem hard and you people who sort of hit a slow point but let's let's get our excitement back up and get moving and I know we have a bunch of really exciting topics still in the class so I apologize that that midterm was too long. I think we haven't fully figured out how to deal with virtual virtual midterms yet. All right. Good. So let's change topics. So let's talk about deadlock. I like to think of this as a deadly type of starvation. The situation as we've been talking about with scheduling as as our main instance certainly last time is a situation where thread weights indefinitely. An example might be a low priority thread waiting for resources constantly and used by a high priority thread. Of course the principal resource being CPU, but other things can be there too. So the situation that could potentially resolve itself as soon as all the high priority threads are gone so it isn't a permanent scenario but it certainly might be annoying and it might be. You know, not what you want because your threads not running deadlock on the other hand is an unresolvable situation that's a starvation situation. And it involves a circular waiting for resources. So if you look here, we have a situation where thread a is waiting for resource to a resource to is owned by thread B and thread B is waiting for resource one resource one is owned by thread a so here's a cycle. The result of this cycle, both thread a and B are sleeping and will never wake up. Okay, because you know a will never get notified that resource two is ready and be will never get notified that resource one is ready, and nobody resolves itself, and nobody's happy. Okay, so notice that deadlock is a type of starvation but not vice versa. Okay, and again starvations can end. They don't have to but they can deadlocks can't. There's no way to fix this cycle I'm showing you here without fundamentally doing something drastic like thread a killing it off. Then thread B could run or I don't know trying to figure out a temporarily take a resource away from somebody and then give it back. Okay, and both of those situations are usually bad just randomly killing a thread, probably isn't what you want to do and randomly taking a resource away from somebody probably gives you bad behavior. Okay, so what's a good example of a resource other than locks and semaphores so we'll talk about memory. You know, disc blocks, pick any resource you like a queue. You know, think anything that you might wait for is a situation where you might be in a run into problems okay. Now, you know another example could be that you're waiting for a particular CPU in some special machine that's attached in a way to some hardware that other CPUs aren't attached to. That could be an important resource that you're waiting for so pretty much anything that you need to complete your task that might need to be exclusively owned counts as a resource here. Okay, that help. Now, here's the simplest example. Here's a bridge. We have a lot of these in California. We just out driving the roads last weekend and encountered one road where there was like three of these single lane bridges, all because parts of the road had washed out. And they never got fixed from the last rainy season so that's unfortunate but you can imagine that this might be a source of deadlock under some circumstances. So for instance, you could view each segment of the road as a resource car has to own the resource that's under it of course and they may need to acquire the segment they're moving into in order to make any progress. Okay, so, for instance, if you have a bridge and let's just divided into halves you have to acquire both halves and traffic only in one direction at a time is clearly going to be required for that. But here's a bridge situation where there's two halves, we have two cars that are on each half, and we have a bad situation here because the two cars are meeting in the middle and can't make any progress. And then I've shown you here a cycle, you know we have the minivan is trying to get the eastern half of the bridge and we have the race cars trying to get the western half of the bridge, the minivan owns the western half because it's on it and the, and the race car owns the eastern half because it's on it, and we have a cycle. Okay, and how do we resolve this deadlock well if we want to resolve the deadlock in a way that's reasonable, one of the cars has to back up. If you get enough, if you get two people that are unwilling to back up then you get a long term honking going on. The other thing to note by the way is because of the ownership of resources prior, it's possible that for instance in order for the green car to back up other cars have to back up. And so there may be a whole chain of resources that have to be relinquished and reacquired only in order to undo that deadlock. And then there are other resources that might have taken a database course like 168 or something like that might recognize that 164 might recognize the situation as some sort of undo, or transaction abort. Okay, 186 that's what I meant sorry. I'm being, I'm being swapped tonight. So, the other thing that can show up in this scenario is starvation if for some reason, one direction, say, you know, east or west to east is just going so fast that no other car gets in that's actually a type of starvation here not deadlock, because, you know, as soon as there's no more of that traffic than the other traffic can go. So let's look at deadlock with locks here since this seems like the simplest thing to start looking at. So here are two threads. Yeah, this is a situation where the municipality might need another lane right well as I mentioned on that road I was on literally. There couldn't be another lane because it was it was washed out, and there was just those Jersey barriers around there to prevent you from going into the creek so I would say the local municipality wasn't able to fix it. So this is a situation where thread a and thread be look as follows they both are have mutex x and y, but thread a doesn't acquire of x and an acquire of why it does some stuff then it releases why and then releases x thread be on the other hand acquires why, and then acquires x does some stuff releases x releases why. So this lock pattern. It seems like something you could write by accident if you weren't thinking about it because you got two resources x and y you need them both. You write one in one order and the other in the other order. And the problem with this is that this is a non deterministic failure. Okay, and there's nothing worse than writing something that fails non deterministically, because you can't reproduce it to start with I'm sure some of you have started to run into problems like that in in the code that you're And, you know, and the other worst thing is it's going to occur at the worst possible time now if you remember when I was telling you about the Murphy's law scheduler or the malicious scheduler view. This is a situation where the scheduler will find the bad situation and they'll do it at the worst possible time. Let me show you a little bit about the unlucky case so thread a acquires x thread be acquires why no notice the interleaving going on here right thread a tries to acquire while why but it's stalled because it why is acquired by be thread be tries to acquire And now the rest of that code never runs. Okay, so this is a deadlock. And if you notice here so thread a is kind of waiting for mutex y and thread B is waiting for mutex x, and neither of these are going to give it up and so basically we are stall. Okay, neither thread gets to run we've got deadlock. So, but let's look at the lucky case. Oops, sorry about threatening me. So the lucky case here thread a acquires x and y, then thread B comes along and acquires why it tries to acquire why but notice that it's stuck. Okay, and then thread a releases why and releases x, then be finally gets to acquire why then it acquires x and it runs, and the schedule doesn't trigger deadlock. And now what's involved in getting that exact deadlock case to happen. Well, the scheduler has to line up at exactly the wrong time with this previous case here to get the deadlock. Most of the time, it won't happen. And you'll get the lucky case. So here you are you ship something to customers. And you get a call at 337 in the morning because the thing has deadlocked because most of the time you're seeing the lucky case but you didn't see the unlucky case when you were testing. Okay. And the larger about a code that isn't in your lock case so like here, you know we have a few instructions here doing locking but we have a lot of code, maybe in the critical section, and a lot of code outside the critical section. No, it's from a probability standpoint it's just not a high probability event but boy when it happens you are toast. Okay. Questions. All right. Everybody good. Now, let me show you another case. Here's a here's another circular dependency. That's a little bit different but it's similar okay and I'll tell you why I'm calling this a wormhole rooted network in a moment but for now this is, there is some trains here they're long trains there's a little tiny train over there too. But these long trains stretch for a while since they're long. And what we've got here is each train is trying to turn right. So this, this eastern facing trains trying to go south the south trains trying to go west, the West trains trying to go north and our strength trying to go east. And they're blocked because the resource they need, which is for instance this West East train is trying to grab that segment. Immediately after the turn but it can't because there's a train in it. Okay. And this is actually a very similar problem to what you get in a multi processor network. Okay, so this is a situation where where you've got basically a wormhole rooted network with messages that trail through the network like a worm. So instead of trains what we've got is we've got a routing flit at the head and then the body of the messages kind of stretch out all the way back to the source of the messages. Okay, so that's called wormhole rooted networking because it looks like a worm, and it's rooted as that worm all the way through the network. Okay, and here we've just developed a deadlock. Okay, so how do we fix this. Well, what you do in the network case this may not be as practical in a train except maybe in the metropolitan area. As you make a grid that extends in all directions, and then you force an ordering of the channels okay and the protocol will be you always go east west first and then north south. Okay, so what we've just done is we've disallowed by this rule, these two parts of the turn so this red turn here and this red turn there so you're not allowed to go north first and then east. You're also not allowed to go south first and then west. And by disallowing those two turns, you will never get deadlock because you can't fundamentally get a cycle out of it. In fact, you can even write a proof that shows that this network has no deadlocks in it because a deadlock would require a cycle and a cycle would always require at least one of these disallowed turns. Now again, this is not as practical in a train network but certainly in a network network if you have a mesh, what you can say is I always have to route east west first and then north south and as a result, you can end up with no deadlocks. Okay, questions. Now, by the way this is a real x y routing is a real thing or you look up dimension ordered routing. There are real networks that behave that way, including, you know, the interior networks that are part of the Intel chips. So this is a this is a real thing and it's a way of avoiding deadlock so it's kind of nice because you can avoid it mathematically. Other types of deadlocks there are many of them right so threads block waiting for resources like locks and terminals and printers and drives and memory. So threads might be blocked waiting for other threads like pipes and sockets. You can deadlock pretty much on anything like that and all it requires is getting some sort of cycle involved. Okay, so we might want to figure out a little bit about how to avoid these kind of deadlocks. Okay, so here's an example of one with space. So here thread a alloc is going to do an allocator wait one megabyte and then another megabyte, and then free free and thread does the same thing well, there's only two megabytes total of space you can imagine that a gets a megabyte then be gets a megabyte. And we're now deadlocked and just the same cycle as before, but it looks a little different. Okay. So let's talk about how to think about cycles that that have resources where there's multiple equivalent pieces of the same resource in a little bit. So, in order to move our way along this let's talk about what I like to call the dining lawyers problem. So we have five chopsticks and five lawyers. And it's a free for all so what we do is we put one chopstick in between each lawyer. Okay, and the lawyers are going to grab and by the way, nothing against lawyers this is just the example here, but you need two chopsticks to eat. And if everybody grabs the chopstick on their right we now have deadlock because nobody can can eat. Okay. So that's a, that's a deadlock and so larger cycle than just, you know, two resources and two threads but it's still a deadlock because there's a cycle. So how do you fix the deadlock well you could make one of them give up a chopstick. And eventually everybody gets a chance to eat. Oh, and by the way this is such a cheap restaurant that you have to share the chopsticks after they've been used and you put them down so perhaps during a pandemic you wouldn't want to do this solution. So how do you prevent a deadlock well that's more interesting right so the way you might prevent a deadlock here is to never let a lawyer take the last chopstick if no hungry lawyer has two chopsticks afterwards. Now wait a minute what does that mean. If you never let a lawyer take the last chopstick. If as a result of taking that no other lawyer has two chopsticks, then you know that there's always somebody that can finish dinner and lay down their two chopsticks and then let somebody else go forward. There's always a solution to this that involves looking ahead that maybe we can formalize in some way. Okay. But to do that we need to talk a little bit more about deadlock so what is required what's the minimum requirements to run into a deadlock. Well first and foremost, mutual exclusion is a requirement so that says that we have resources that can be possessed exclusively by one thread, such that no other thread can use them. Okay, so remember we've been talking about mutual exclusion as a way of keeping a multiple threads out from the middle of a particular block of code. So this is the same idea, but this is for general resources we're saying that we have resources that can be mutually held on to by one thread, and requested by another but not acquired until the first threads done with them that's mutual exclusion. The second is this idea of hold and wait, which says that if a thread has multiple resources it's already acquired and it's waiting to acquire another one, then it's going to hold on to all the resources that it's got. What happens is it grab grab grab resources tries to grab the next one, and it can't, but it's going to hold on to all the other ones okay so you need to not only have mutual exclusion of resources but you got to be able to have a situation where you hold them and wait on them. There also needs to be a situation with no preemption so not only do you hold resources. Well you're waiting for other ones but it's not possible to take a resource away from somebody. And that's kind of like if you think about the bridge example. What would be, what would be a preemption case there well that would be Godzilla comes by grabs one of the cars that is honking and tosses it into the other valley. And now we've just broken the deadlock okay so we're assuming that something like that can't happen. I'm assuming you all know who Godzilla is but perhaps I'm dating myself on that. So the third thing, there's you need for things gives me is you need to have a circular weight, where there exists some set of threads that are waiting T one through N, where T ones waiting for something held by T two T two is waiting for something held by T three, etc. T and is waiting for something held by T one, etc. All right. And now, as a result. We, we have a cycle. Okay, if you don't have a cycle of waiting there is no deadlock. Now what I want to make sure I'm clear on here is, you can have all these things and not have deadlock but if you don't have one of these things you don't have deadlock. Okay, so these are minimum requirements, but they're not sufficient. They're just, they're just necessary. Okay. And if you were to think through all of the examples of deadlock I've shown so far. It had all of these properties to it. So let's talk about how to detect deadlock and to do that we're going to build a resource allocation graph. So here's our model. We have threads which are going to be circles with T sub something in them. These are the resources which are going to be rectangles, and we'll call them our one or two, etc. And notice the number of dots in the rectangle represents the number of instances of that resource in the system. So, these are all equivalent. Excuse me so in the case of memory. For example, I showed you a little bit ago, where we were allocating one megabyte and then one megabyte and then one megabyte each megabyte is equivalent in those instances. So we would build that as a rectangle with a bunch of dots representing all the equivalent megabytes, and we call that a resource. Okay. The resources which were mutexes or locks that we were talking about earlier might be an example here of a square with a single dot in it. Okay. Every thread is going to utilize a resource by first requesting it, then using it, then releasing it. Okay, and this notion of request use release is kind of that that idea of mutual exclusion where between request and release, if I'm in the use phase. Nobody else can use that particular resource, but that means a particular dot is now used not all of the resources that are equivalent. So a resource allocation graph is very simple. Okay, it's a it's partitioned into two types of nodes T nodes and our nodes, and we're going to build that graph where there's a request edge, which is sort of T one to our J and that basically says that thread one wants resource J, or an assignment edge our J to T one T I, which basically says that our J is owned by T I. Okay, and that's going to build a graph for us and then we're going to go through that graph and figure out whether we have deadlock. Okay. I have some examples here so remember the model is request edge and assignment edges. And so here's a simple example. So here's an example of threads one two and three thread one is requesting resource one. That's what this request edge looks like here we have an assignment edge R one is owned by T two. Okay, so this is and everybody see that so here's an instance where our four there's three possibilities there but only one of them is currently owned by T three. Okay, everybody with me so far. Now once we have a graph like this then we can do graph operations on it and very quickly decide whether it's deadlocked. So for instance, here's an example of a graph with a deadlock. Now it's not your simple deadlock. But if you look here, it's got T one is waiting for our one but our ones owned by T two. Our threes owned by T one one of the instances and the other instance of our threes owned by T two T two is waiting for our two but our two is waiting for teeth is owned by T three. And if you look at this scenario, this is an unresolvable situation where there's no way that any of the threads can advance and make forward progress. Now, so good question. So the question was so a cycle leads to deadlock. No, a deadlock needs a cycle. It's important here. A cycle is merely necessary for deadlock not sufficient. So for instance, good question I clearly paid him for to ask that question. If you look here here's an example of a cycle but no deadlock. So notice that T one is waiting for our one, our ones owned by T three one of our ones is owned by T three T three is waiting for our two one of the R twos is owned by T one so there's a cycle here. But what we also see is that if T four finishes, it'll free up an R two and then T three can get what it needs and it'll finish and then it can free up an R one and then T one can finish. So just because you have a cycle doesn't mean you have a deadlock. But if you have a deadlock you know you have a cycle. Good. So, now we have we're armed and we can figure out how to detect deadlock right so here's a simple algorithm. And what is the key thing about this algorithm is just understanding the symbols here. So I'm going to have a vector of resources so this is a vector, it's going to be a comma comma separated list. And for each resource our one or two or three or four. I'm going to say how many of those resources are free. So in this case here are one and our two are completely taken so we're going to have zero comma zero. We also have current requests from thread X. So if you notice for instance T one is currently requesting an R one. So we're going to, but it's not requesting an R two so the request for T one is going to be one comma zero. The allocation for T one well it owns an R two but not an R one, the allocation will be zero comma one. Okay, so these are just vectors of free resources, numbers of free resources and how much being requested and how much is allocated by each thread. So if you can get past that, then it's very easy to do this we just do a list based algorithm where we set the we say the total number of available resources is the vector of free resources. We put all the nodes, excuse me I should say all the threads into the unfinished bin, and then we're going to do, we're going to start by saying well I'm done equal true. And then I'm going to go through and for every node that's in the unfinished bin I'm going to say well, is there enough nodes available enough resources available of each type that I can get what I want my I'm currently requesting. And if the answer is yes, then I finish I figure out that as a thread I can get all those resources. So I'm going to be able to finish and I'm going to renew remove the node from the unfinished bin, and then I'm going to add all of its resources back into the available pot because I'm now done. I'm going to say with that thread. I'm going to say that I'm done with this algorithm and a set it to false, and, and then I'm going to go keep going. And then with the first do loop, I'm going to say gee did was there any thread that finished as a result of going through. If the answer is no, then I'm done, and as a result I've got some nodes left and unfinished and I'm deadlock because there's no way to finish this. On the other hand, if I did pull a thread out in that pass I go back and try it again and I just keep looping, as long as threads are finishing and if I eventually finish everybody, then I know there's no deadlock. How do I know there's no deadlock because there is a path where threads can complete one at a time, and will eventually everybody will be finished, and I won't exceed the, the total resources in the system. And each thread is it finishes puts the resources back in the pot, and, and then potentially those can be used by other threads. Okay, and if I did that. Let's see. So the question here is basically, this is all fine and dandy but is it possible that we could have a situation where one thread gets a resource. And as a result, other threads can't finish and you end up with deadlock I think that was the question. And the answer here is if you notice this deadlock algorithm is very careful. Okay, it's saying, if a thread can get all of the things it needs. All of it right all of its requested remaining resources can get them all at once, then I'll declare it finished and put all of its resources back in the pot. And then as a result, I haven't prevented anybody else from running all I've done is freed up my own resources which they might potentially use. Okay, so this particular deadlock detection algorithm is saying, is there any path that I could take through the threads that would let them all finish. Okay, did that answer that question. Great. So, how do we. So we can detect deadlock but how do we deal with it Oh by the way. Can anybody tell me if I run this algorithm, and I see there is no deadlock according to this algorithm does that mean that all threads will finish. I've got both yes and no on here. Okay. Anybody want to argue, both with a question mark. Nobody wants to argue. Okay, so the answer to this is no. But it's not the fault of the algorithm. Okay, you got to be careful about what is this algorithm telling me it's telling me that if the threads are asking for resources they need they use the resources they free them up. And if the other threads can go forward we're all happy. But if a thread goes into an infinite loop, or something else happens, or doesn't free up the resources for some reason then this algorithm really doesn't tell you anything right so this, this algorithm is assuming that the threads are really just requesting resources and freeing them up and not doing anything else stupid like going into an infinite loop. Okay, so or asking for more things than they originally said they need. Okay, so this is, this is a very restricted algorithm algorithmic result here. Okay. Now how should a system deal once it's discovered deadlock okay we have four approaches here that I wanted to mention one is deadlock prevention. So this is a situation where you write your code in a way so it will never deadlock. Okay, now I think I showed you that earlier. When we talked about removing the cycles from the network by eliminating certain directions of travel right so that would be a prevention scenario. Deadlock recovery is a situation where you let the deadlock happen and then you figure out how to recover from it. Okay that's the Godzilla approach. Deadlock prevention is dynamically delay the resource request so that even though in principle you could get a deadlock, it doesn't happen. And then finally, I like to put this last one out because this one's important and you should all know this exists I call this deadlock denial, or deadlock denialism. Okay, so this is ignoring the possibility of deadlock and claiming that it never happens. So, modern operating systems kind of make sure the system itself isn't involved in deadlocks and then pretty much ignores all the other deadlock and applications I like to call this the ostrich algorithm. Okay. So, this is why sometimes you have something running and you got to reboot the operating system to fix something okay that that oftentimes is because there's some deadlock that nobody planned for nobody detected and nobody had any way to deal with other than just rebooting things. And unfortunately that's a much more common than you might think. So let's talk a little bit about prevention here for instance. So one thing you could do is put infinite resources together. Okay, so that's, you know, infinite big right but what we're really saying is you include enough resources so that no one ever really runs out of them doesn't have to be infinite just really big. And you give the illusion of infinite resources so a nice example that might be virtual memory, which under most circumstances appears pretty big. Right. Another somewhat less practical example might be the Baybridge with 12,000 lanes, you never wait. Okay, so that might be nice. Never gonna happen right infinite disk space well we're pretty close to that in a lot of instances right you can buy 100 terabyte. You can buy these days that's using flash memory, and that's pretty big. Okay. You could decide to never share resources if you think about the cycles we're talking about earlier cycles require that there's a resource that's being used by one person that is somebody else right if you never have any need for sharing you'll never have any deadlock because you can't come up with a cycle. Another option would be never allow waiting so notice what I'm doing here by the way is I'm removing one of those four requirements for deadlock right so not allowing waiting is really how phone systems work. It used to be a lot more common. It still happens occasionally where you try to call somebody and it the call phone call actually works its way through the phone network, but it gets blocked somewhere because they're not enough resources and what happens is you get a busy single. What's really happening there is it's it bounces the call, and it assumes that you're going to retry by making the call again. What they've done there is they've avoided deadlock in the network by pushing you off to doing retry. Okay. This is actually the technique used in ethernet and some multi processor networks where you allow everybody to speak at once on a segment. And if there's a collision then what happens is you exponentially back off with some randomness and retry and as a result the problem goes away. Okay, so this is a technique of random retry instead of a potential deadlock. We'll talk a little bit more about that later in the term. Now it can be inefficient. If you don't use the right algorithms, you know the goofy thing here is you consider driving to San Francisco and the moment you hit a traffic jam, you're instantly teleported back home and have to retry. That would be an example of you know retry mechanism that probably would never work because you could never make it through. So if you're really going to reject and force a retry there has to be some notion that there's going to be eventual success on that channel. Okay. So here's an example of that virtually infinite resource I mentioned earlier while we said this could deadlock if there's only two megabytes in the system, but with virtual memory you have effectively infinite space. So, everyone just gets to go through and you won't deadlock. Okay, now of course it's not actually infinite but it's certainly a lot larger than two megabytes. So maybe this is a little more interesting. So you make all threads request everything they need at the beginning. And then you check and see if you've got enough resources and if you do you get to go and if you don't you don't. Okay, so if you think about that they'll never be a situation where you're in the middle of execution. You have some resources you're waiting for others, you've just basically remove the weight portion of that cycle. So the problem here of course is predicting the future as to what you need for resources and you often end up overestimating. For the example if you need to chopsticks you request both at the same time. That may or may not work well. You have imagine reserving the Bay Bridge that wouldn't work too well either. You don't leave home until you know no one's using any intersection between here and where you want to go. And of course, pretty well if you're traveling around, you know, 130 at night across the Bay Bridge. There might be enough channels there or or lanes to know for sure you're going to make it without being delayed. You could force all the threads to request resources in a particular order. All right, well this is more interesting so for instance to prevent deadlock, you always acquire X and then why and then Z. So if you always acquire X and then why then Z, you can prove fairly simply that that'll never deadlock, because any deadlock involving those resources would have to be a cycle. And therefore a cycle would mean that some thread acquired something like Z, and then acquired X, or Z and then acquired Y. So any actual cycle in a supposed deadlock would show you going backwards in your acquisition and as a result can't happen because you always have to get X and then why and then see. And this by the way is exactly that dimension order routing that we talked about in multi processor or train networks earlier. Right. So for example, so rather than what we showed earlier where you could get X and then why and for today and then why and then X and B, you just maybe acquire them both. Okay, so here we get both X and Y both at Y and X whatever either you get them all or you don't. And as a result there's no cycle that's the first thing I showed you. The second was, you maybe get a lock around you grab Z. For example around Z and if you happen to acquire Z then you can acquire what you want. Okay. And that won't deadlock because there's no cycle. Or, here's the consistent order so rather X than Y, Y than X what you do is you always go X than Y, X than Y. Okay, and as a result it'll never deadlock. Okay. Now does it matter which order the locks are released. Like notice here, we always go X than Y, X than Y but here I'm releasing why the next and here I'm releasing X than why the matter. Okay, good, doesn't matter, because the only thing I do with releasing is I'm letting people go forward I'm not holding them up. Right so the releasing can be done in any order it's the acquisition that has to happen in the same order. I will say however though, typically you acquire them in one order and you release them in another and that's just a way of making sure that you've got a nice clean pattern there. And this is kind of what we looked at when we were talking about the finite buffer queue and we're when we're looking at semaphores a little while ago. So train example, right here's the fixing ordering of the channels you're always getting the X channel and then the Y channel. And as a result there's no way to have a cycle because any cycle would show you having the Y channel first and then the X channel. Okay. All right. And this works in multiple dimensions, you know more dimensions you can have X, Y, Z, W whatever, as long as you get you get them in a given order then you don't have deadlock. So here you can terminate the thread, force it to give up resources so that I told you about the Godzilla solution earlier. You hold the dining lawyer and contempt and take a take them away and handcuffs you make sure you get the chopsticks first. But it's not always possible because killing a thread holding a mutex would actually leave things inconsistent and probably screw everything else up. Okay, so taking things away is rarely a good thing. You could preempt resources without killing the thread but then again the resources think they have the resources, excuse me the threads think they have the resources exclusively, you've just taken them away. The threads going to not behave correctly. So the one case where this actually works out well is when you have enough information to do a full rollback or an abort. And this is sort of the database idea where before you grab your locks you have a checkpoint of the of the state. And now you, you go ahead and start running. If there's ever a deadlock you roll back to a time prior to the deadlock and you restart things. You can do with some randomness so the deadlock doesn't happen again. This is a very common technique, the databases can use because they can roll back to a consistent state before they retry after detecting a deadlock so this is the one instance where you can just back up and take it away and retry it and make sure the deadlock doesn't happen again. Many operating systems have other options. But I will say that UNIX operations operating systems often use the denialism technique. So, you know, here's this other view of virtual memory so we said well we could think of instrumented space this isn't a problem. One level deep which will be appropriate, one level deeper which will be appropriate in the next lecture is we could say that what actually happens when we run out of memory for one of the threads is we preempt that memory, paging it out to disk and give it back later when we page it back in. And as a result we can take memory from one thread that means now DRAM physical memory give it to a different thread and everything's okay because when we come back, we grab the data and give it back to the thread we took it away from, and we don't let the thread look at it in the middle and so we find a way to suspend the use, save the state, and avoid the deadlock. Okay, and that's kind of what paging does. Let's talk about avoiding. Okay, so when a thread request the resource the operating system checks and sees would it result in deadlock. If not, it grants the resource right away. If so, if there's going to be a deadlock it waits for the other threads to release resources so this almost sounds good. Right, so the idea is we somehow look, and we say will this, will this thing we're giving it have a deadlock. If so, don't give it the resource otherwise do. And the issue here is let's show this example. Here's our thread A and B that could deadlock. We acquire X, no deadlock there. We acquire Y, there's no deadlock there, there's no cycle. We acquire Y, still no deadlock. Okay, now notice at this point thread A is blocking because it's trying to acquire resource B has we still don't have a cycle, because B is happily running. Here, we say oh, if we acquire X we're going to have a cycle and therefore deadlock so we'll wait. The reason is it's already too late because there's already impossible. It's already impossible for this situation to resolve even though there isn't a cycle at the moment you try to acquire X. So we have to do something a little better here. And so here I'm going to introduce three states there's the safe state which is the system can delay the resource acquisition to prevent deadlock. And this is a situation where we can make forward progress and we won't deadlock. There's deadlock where we're in trouble right and we already have a cycle. And then there's an unsafe state where there's no deadlock yet, but threads could request resources in a pattern that will unavoidably lead to deadlock. That's what we had in that previous slide we already had an unsafe state. Okay, and actually the deadlock states considered unsafe as well because once you're deadlocked it's not safe. So deadlock avoidance is preventing the system from reaching an unsafe state. How do we do that. So for instance when a thread request the resource OS checks and sees if it would result in an unsafe state. If not it grants the resource if so it waits. So how this changes our example is thread a grabs X. Everybody's good. thread B tries to grab why but we look and we say oh, if we acquire why we're already down the path to an inevitable and inevitable deadlock and therefore thread B is not even allowed to acquire why. Okay, if we could come up with that, then what happens is be goes, you know, to sleep and it's stalled a goes on to acquire why does its thing releases the two. At that point be gets released and now we're good to go and we don't have any deadlock. So this algorithm that I've sort of implied has somehow kept us in a safe state and therefore we don't deadlock and that's kind of what we'd like to do. So we have something called the baker's algorithm. So toward the right idea is state the maximum at the beginning of resources you need, and you allow a particular thread to proceed only if the total number of resources, minus the number I'm requesting, still says that the remaining is greater than the maximum that anybody needs. So we take the current current available minus what I'm asking for and as long as what's left is greater or that are equal to the max and anybody will need, I'm good to go. Why is that okay well that says basically that. Gee, even though I've been given these resources. There's always somebody that can complete. So this is not quite what we want. This is a little too conservative. Okay. Instead the banker's algorithms a little less conservative and it's going to let you ask for resources free him asked for him free him so on. And what we're going to do is every time somebody asked for a request will grant it as long as there is some way for the threads to run such that they will complete without a deadlock. So we only run, we only grant a resource if there's some way to complete without a deadlock. So the technique here is to pretend that we grant the resource that's being asked for, and then we run the deadlock detection algorithm. And in that case we're going to substitute this which is say take the maximum that anybody wants. Take the maximum that a given node wants minus the amount they have. And see whether it's less than what's available and we're going to replace that for what we asked about earlier which is, you know, seeing whether what we're requesting is less than what's available. Okay, and so here, notice that in this deadlock detection algorithm we're going to say that for every node, instead of if requesting the amount we're requesting is less than what's available we're going to say if we take the maximum we need minus how much we have is less than what's available, then we can finish. Okay. And so this is like that simulation that I talked about earlier where for we temporarily grant the thread that's asking for something and then we go through and we say is there a way to let some thread finish and then let some other thread finish and so on. And as long as there's still a path through that allow the thread some the whole set of threads to finish we're not deadlocked and we're still in a safe state. Okay. So basically that algorithm as simple as it is which is substituting this into the deadlock freedom algorithm keeps the system in a safe state which says there's always a sequence T1 T2 to TN, where T1 completes then T2 completes and so on. There's always a path out, even if I pretend to give the resource. And if that's true, then I go ahead and give the resource. Okay, so the way you need to think of the bankers algorithm. I realize I'm a little bit low on time just give me a few more minutes. The way to think about the bankers algorithm is that it's a simulation of what would happen if I granted that resource to the thread would I still be able to find a way out such that every thread completes and if the answers are correct then go ahead and give it. Okay. And this is a an actual algorithm that we could run on every acquire and release of every resource that would prevent us from deadlocking and it would actually do that run that example I showed you earlier, where we grab X and grab why and in the other case, this particular algorithm would actually prevent that from ever deadlocking because thread B would be forced to wait until thread A was fully done in that instance. Okay. So in some examples here, if you think through the bankers algorithm what you would find is that a safe state which is one that doesn't cause deadlock is if when you try to grab a chopstick either it's not the last chopstick, or it is the last chopstick else has two. If either of these conditions are correct, then you're going to still be in a safe state and you can allow that chopstick to be acquired. Where this gets a little bit amusing as you can imagine the K handed lawyer case, which is you don't allow if it's the last one and no one would have K chopsticks, or it's the second the last one and no one would have K minus one and so on. Okay. We're about done here. Yes, we can actually do a K handed lawyers case. So I want to pause for two seconds about whether there's any questions here and then I'm going to finish up. So you have to think about the way you think about the bankers algorithm is on every acquire or release of every resource. If you pretend that you give that resource. The Colonel does this it pretends it's going to give that resource that runs that special deadlock detection algorithm and says am I going to go into deadlock. Or is there a path out of this if there's a path out, then it will grant the resource if there is not a path out that it won't grant the resource and instead put that thread to sleep until there are enough resources. Good. So, the question of course is do most of us is implement this so as I already told you Unix essentially uses the ostrich approach or deadlock denialism. However, if you cared you can implement this so there are some specialized OS is that do do this. The second thing that's kind of interesting is I think shown by this page here, which is you can use that the bankers algorithm to design a way of accessing resources that won't deadlock so you can use the bankers algorithm as a way of designing how you go about asking for resources in a given in a given application. Then you don't actually need the bankers algorithm running live because you've set it up so that it's running as part of your actual application. Okay. And you could even run a bankers algorithm library inside of a cell of an application instead of the operating system. Alright, so we talked about the four conditions for deadlocks we talked about mutual exclusion, which is that when you get a resource you have exclusively hold of it, hold and wait which is I hold on to other resources while I'm waiting for the ones that I'm looking for no preemption says I can't take resources away circular weight there's at least a cycle in the system. All four of these are required. These are necessary but not sufficient for deadlock. We talked about techniques for for basically addressing the deadlock problem. You can either prevent it by writing our code so it won't be prone to deadlock, we can. That includes things like dimension or routing, we can avoid. So that's avoid the deadlock we can recover from deadlocks by rolling back, we can avoid it entirely by something like the bankers algorithm or we can totally ignore the possibility, which unfortunately, a lot of things do. Alright, I think we're good for today, and look forward to the results of the midterm grading coming out and again it was a little longer than we intended we apologize for that. I hope you have a great weekend. I hope that you can get outside and that the air improves a little bit.