Mutex Synchronization in Linux with Pthreads

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello I'm dr. Brian Frazer and in this demo I'm going to show how to use P threads and the mutex to synchronized processing between the threads so initially here I've got Eclipse open on the Left I've created a project that's going to build from a make file and I'm going to sort of start with this application that I've written here we can trace it through we can see here in main I'm going to call this function called counting function and I'm passing it a single argument and my counting function simply loops through quite a number of times here I think it's f5 million times or 500 million times it loops through for I equals 0 I less than 500 million I plus plus and I'm going to add to my variable sum which happens to be a global up here the value that I passed in which is the argument offset so offset came in in this case offset is 1 so all I'm really doing is I'm going to call this function that adds 1 to the value sum 500 million times so we're going to expect it to end up with being 500 million once that function is completed we're going to come down here and print it out I'm using lld here that is the format specifier conversion specifier for a long long printed as a decimal so lld and that's so that I can actually print out some here I want to use a long long so that I can have later on if I'm key doing a lot more counting we don't have overflow issues okay so let me build that I'll come over here on a short the command I make command execute the actual command I'm using currently to build it is this so you can type that in on the command line if you didn't want to use a make file and it would build it for you so I can run my program count it takes a moment to run and we can see here that the answer comes out as 500 million just exactly what we wanted it to do okay so that's the easy thing to do let's now kind of start getting into what I want to demonstrate so this is a single threaded application so let's see what happens if let me start off with this I'm going to time it so I can type time dot slash count and this will use the Linux command time tell me how long it took so the real-time total time executing was one second one point 1 9 second so 1.2 seconds basically so what I'm going to do is I'm going to come down here and I'm going to change this to call it a second time I'm going to pass in negative 1 rebuild with control B or control em in now control B pardon me in eclipse and then I'm going to come back and Reax acute the time command so we can see here that the sum comes up to 0 because I first counted up by one five hundred million times and then I counted down by one five hundred million times putting me back exactly at zero and it took just over two and a half so just about two and a half seconds to run and I run this a few times I think two and 1/2 seconds each time approximately slightly different each time due to scheduling differences but each time the sum comes out to zero because my code is basically correct right it does what it should it accesses the global variable adds one subtracts one no problem there at all now let's say we wanted to bring in threads so with threads we can put some compute computation in a background thread or a secondary thread so we're going to start by commenting out this counting function second one here what we're going to do is we're going to convert our counting function to being a thread function what that means is simply this is going to be the function that gets executed when a thread begins processing so we can come in here we can say well instead of if we look at the prototypes and so forth for P threads you can check out my other videos should be linked below on setting up P threads we need to return a void star instead of us taking in an integer we need to take in a void star which is gonna be the argument I still want this offset for the rest of my code but I need to get that from the arc now I've got a pointer to avoid so I need to make that a pointer to an int because that's what it was getting passed in I'll be a pointer to an int but then I need to dereference it so this will take Arg make it a pointer to an int and then do you reference it which is to say follow the pointer and give me the value and put that in the offset here and then offset then just processes as normal and at the end we need to return a value so the way that you often end exit a thread function is you call pthread exit and pass some value I don't need anything so I'm going to pass it all just saying just exit I'm not passing anything back which is effectively whatever I pass in here is going to be the what's returned by my thread function effectively okay so a couple other things I need to do I've started to use thread so let's actually launch the thread down here so I'm gonna use P thread I'm just create I need to pass in the ID of the thread ID so I need to create one so P thread let's check that out so what's it going to be it's going to be P thread type type deafed and let's call this one ID one just for fun and here I'm going to pass in a pointer to that if I hit control space Eclipse tells me that this should really be a pointer to a P thread object so we're going to have the address of ID 1 next I need to pass in the attributes I'm not going to pass any attributes in so I'm going to pass in null I need to pick the thread function so this is currently called counting function importantly I don't want to pass in brackets I just want the function here let me expand eclipse just a little and let's minimize that to give ourselves some bit more room and then finally I need to pass in an argument well I'd often pass and know if the whole threading function was self-contained but here I do need to pass in an argument I need to pass in a pointer to a value so let's make a value here int and shall we call this one offset offset one y naught equals 1 and now I need to pass in an address of that so off offset 1 now it needs to be a void star but you can cast anything to and from void star quite nicely and see quite permissively I might say so that will do this so effectively what I'm doing is I'm converting this one line counting function 1 instead of it being a normal function call I'm going to switch it to a threading kind of framework so here I have my code to launch the thread I've already changed my prototype to handle a thread so I can get rid of this don't need that anymore and then the last thing I need to do is down here I'm going to a pthread join which will force the main thread to wait for the background thread to finish and I need to pass in let's see here we need the pointer to the don't we just need the thread ID so I can pass in ID one and then I can accept some sort of pointer back I'm not passing anything back so I'm going to pass null or accept null here that'll tell P threads on I don't want anything okay so we're getting there now if I try to build this it's not going to work I can scroll back through all of my big errors here and it tells me implicit declaration of P thread exit now this is partly due to I've set to turn on all warnings and turn on treat all warnings as errors this is a great idea because it forces you to actually get correct code so what I need to do here effectively if it tells me implicit declaration I'm probably missing it include some hash include' pthread dot H so now if I try building that still fails see why this is undefined reference to P threat now undefined reference this is actually in the linking phase so if I scroll back to the left here it's going to be in the linking phase it's not showing us anything different here what I need to do is I need to tell GCC that when it links us together to include the P thread library I'm going to come into my make file and I'm going to add the argument - P thread which adds all of the P threads support this is the make file to actually build this I'm saying here I'm building the target count and I'll save that village function okay let's go back to the make for the command line here just show it's gonna look like so make clean and they'll type make so I'm executing this command and to build it you can see the - P thread here is the extra argument we just added for the build so now if I run this sewing two time for example dot count we can see that we come up with 500 million and we're approximately at the same time we were before if I run this a few times it's going to have seemed somewhat consistent timing maybe takes slightly longer to run than before but that's okay but we're only executing half the problem we're only currently counting up well if we wanted to also count down we effectively duplicate the work we just did so we get rid of that call here what I'm going to do well let's just for fun start this off I'm going to take all this code duplicate it change all my IDs from ones to twos if I was trying to do this in general I'd probably create an array but I'm just going to use a couple threads here so that should be fine so let's double check what I've got here I'm creating a second thread ID setting the offset here to be negative one so it's going to try and counting down I'm then going to create a thread using the exact same function so it's a same threading function but sort of a different instance of it so second theory thread I'm going to then pass in the thread ID and the offset the new offset I've created and I'm going to join on it so this is going to have a problem for me what I'm going to do is I'm going to count all the way up on the thread I'm going to wait for it to finish I'm then going to count all the way down and wait for it to finish so this is really no more efficient right I'm just well my main thread is waiting a background thread is doing some processing that's a complete waste of threads I'm not really exploiting anything not getting anything from this so what I'm going to do is I'm going to make it so going to have two basic sections here I'm going to spawn threads and then I'm going to wait for threads to finish wait for threads to finish and so to do that I can move my joins down here the order is irrelevant from my joints it'll simply wait for one then finish waiting for the other and they should finish approximately the same time now the one last thing here I wanted to do before we actually run this is this is no longer my counting function I can name this my counting thread so did control our shift all alt shift r3 name and eclipse and I'm renaming all of these two accountants accounting thread just to make it a little clearer so save that come back to my command line make now let's do time let's start off with just running it see what happens now interesting I'm getting the wrong out let's run the time the sum is coming up to the wrong answer we can see though that it's not taking a lot longer than it took with a single thread so up here when it was doing the single thread it took 1.2 seconds now it's taking 1.7 seconds I can show you kind of what's going on here I can run my system monitor so when to the Ubuntu start menu and run system monitor make it so it fits on the left here so you can see I've got this is a virtual machine running Ubuntu I've got four processors map CPUs mapped to this virtual machine when I rerun now two of the threads jump up they will sort of finish at the same time but maybe you can just barely see in here that two threads have gone sky-high instead of one thread before so I'm effectively using two threads and or two processors in parallel but of course the problem there is a problem and that is the fact that my sum is no longer zero it should be zero because I've got one thread that's counting up at 1 1 3 that's counting down at negative 1 so if I run this again we'll see that it keeps changing back different numbers every time it seems somewhat random so this is a race condition where the order of execution makes a big difference in the actual processing so what do we solve this with well we need some way of doing synchronization we have a critical section problem here we need to identify what the critical section is and then fix it with some form of locking mechanism so a critical section happens when you have shared data between multiple threads or processes in this case I got multiple process or multiple threads pardon me and they're all using this variable sum everything else is separated we've got separate instances of our thread function running we've got separate variables being passed in we've got local variables used here the only thing that's shared is sum so I need to protect access to some in some form of critical section so I'm going to say start critical section here we do some work and then I end critical section so I'm going to use a pea thread mutex for this it's a simplest form of locking the list ensure mutual exclusion so to begin with here let's set up a pea thread so I'm gonna say pthread mutex let's call it mutex and I need to initialize it and there's a handy default initialization which you can use pthread underscore mutex like control space here initialize so this will create me a oops and this would be a pthread mutex underscore t because it's a type def okay so that will generate me a mutex that i can then work with let's compile that of course we haven't done anything with a mutex yet I need to in my critical section lock the mutex so I can use pthread mutex lock here I pass in well let's see what we need to pass in we can come to a command prompt type man pthread mutex lock sotp to add meat eggs and three no okay I think you just do me too pthread yeah okay I had gotten it earlier anyway we can check the documentation for that and find it out so in here I'll just use eclipse control space it wants a pointer to a mutex so I'm going to pass in here the pointer to my mutex named mutex the address of my mutex I'm going to duplicate that the opposite function from lock is unlock and so now I will unlock my mutex so all I did is I created a mutex before I use the shared resource I lock the mutex and then when I'm done using it i unlock the mutex this forces all access to this these lines of code in this case there's only one line of code but this line of code is then protected by a mutex meaning only one thread can access or execute this line at once so let's build that and over here I'm going to rerun my time command so before it took 1.7 second at one point eight seconds now it's taken quite a bit longer let's switch back to our v2 we can see that a fair amount of activities going on on the threads going through doing a lot of processing we see that they now switched cores here so you need to take a very very long time now let's check the code here while this runs just see if we screwed anything up so coming in I initialize my mutex so the Mew takes an initializer down here I correctly lock and start on my critical section I call mutex unlock here to unlock it we see back here and whoa it took 45 seconds however it did the sum correctly so we went from one second to 45 seconds just by adding in a mutex lock which is quite a huge increase now what we're really doing here is we're adding to system calls in our loop so that's quite a lot of overhead so I don't want to go quite so many times through so I'm going to put this back so instead of 50 I'm going to switch this back to let's go to 20 million just to make it so it runs faster so I'm not doing a lot less processing here we're down to two seconds on my processing so now what have I done is I took this parallel function my threads and I made it so that they didn't clobber each other now you could argue wait hang on I went from a previous solution way back at the top and took what you know two seconds or so and I went up to 45 seconds here now if in some cases this wouldn't be a reasonable idea but if you do need multiple threads for whatever you're doing and you don't have quite the same level of sort of shared data as I've got here having those the multiple threads can really improve the computation speed of your process in this case it doesn't but it's just a toy example because I'm doing so many millions literally of my mutex lock and unlock now going from here up here when I had the no locking my mutex just ran or no with no mutex pardon me - down here with the mutex the big difference of course is that we now have the correct answer so it really doesn't matter how slow your code is in some cases as long get the right answer the right answer is the most important thing and then anything you need to do to get there is in one sense a requirement so yes it takes lot longer but it is absolutely critical in order to get the right answer okay so that's the big thing I wanted to show with the mutex the locking and so forth I want to show a few other things that can really go wrong in terms of accessing threads so if I come down here the first thing I wanted to show is at the moment I'm creating a variable for offset one and passing that in here I'm creating a new one for offset two and passing it in here so you might be tempted to think well what about if I simply use the same value again so I can say here offset 1 equals negative 1 and in here let me to duplicate that line I'm going to pass in instead of offset 1 or 2 I'm going to pass an offset 1 so I'm just reusing this offset 1 so this seems like it might work fine but realize that we're passing in an address to it not just the value we're not passing by value we're passing by a pointer and when we pass by pointer that pointer actually comes in here as the address that I'm accepting I then convert it and pull the value out so if we execute pthread create and then this thread starts up and gets control execute this line of code does something else and then after that we spawn a second thread we'll have no problem because the first thread will have already captured the value of offset into its local variable but what can often happen is the bet this thread up here my first thread does not get spawned or does not get executed before we've actually spawned a second thread so effectively my main thread comes through creates one thread that creates a second thread and then only then do either the threads begin executing so let's see what happens when I do this when I run this code so if I run my timer here we see in this case that it came out to negative 440 million which is because my constant here is 20 million now if I run this a few times it may not always give me the same answer let me speed this up just by making it 10 times faster and get rid of a zero there now let's just run count now here it's fairly consistently giving me this value but sometimes when you run it depending on the scheduling and other things on the system like there it'll give you the value zero because in these cases let's minimize that to give us some space whenever we get the zero what is going on is the first thread we create it we execute a little of it we grab as a into a local variable the value of the offset and then and only then do we create the second thread in most cases however we create one thread we create the second thread and only then do the threads get a chance to execute by this time they're accepting the same address they got the same pointer coming in and that variable and memory has changed so this is also a race condition because the order of execution changes what happens so that's why we're going to go back and I will comment these out I need that one done' and we'll go back to this so that no matter how many times i make i run it it's going to always give me the correct answer it's going to be very consistent on this now i could test this out by for example putting this in a loop and running at a million times improving the answers always sum that's a fairly not a proof by any means but at least it gives you a little bit of confidence that you haven't screwed up a case that comes up reefs reasonably frequently okay the last thing i wanted to show was what if we were in a condition that didn't seem like it needed a mutex so i'm going to change this number of loops down to 100 just 100 of course now when I make runs incredible make now it runs incredibly fast and we can see here it seems to work all the time now oops let's actually go through this and get rid of my mutex so we have the exact same code as before we're just running it very very quickly a lot less times through so I'll make and we'll see here that it seems to work and in fact I can run this quite a few times and it might seem to work most times if I ran it a million times though it might fail very infrequently so why why does this seem to work with a very low count but when we have a high count on the loops it fails well here we're only doing it a hundred times this piece of code can run very very quickly and in fact it probably runs inside of we talk about sort of time slices even though Linux isn't currently using the time slice idea but in sort of one-time slicer one kick at the can on the processor we can probably execute all processing of this thread so this thread will go through in one burst probably before the other thread actually gets a chance to run and we have therefore got a lot less of an opportunity to have this synchronization problem crop up so even though it may seem to work in a number of your test cases you may have a lingering bug here when you start running this on multiple processors maybe it starts to happen one in a million times if you start running it on a system that's under heavy load it may also happen more often if the processors are being switched to multiplex between threads much more frequently so even if it's not actually failing for you it doesn't mean that it won't fail on some system which is why synchronization bugs are very very hard you want to be incredibly careful anytime you have multiple threads accessing some shared piece of data okay that's all I wanted to mention I have a look below for some other videos on threads and processes and have a great day
Info
Channel: Brian Fraser
Views: 136,486
Rating: 4.9189053 out of 5
Keywords: GNU/Linux (Operating System), pthread, Mutex, C (Programming Language)
Id: GXXE42bkqQk
Channel Id: undefined
Length: 25min 7sec (1507 seconds)
Published: Tue Feb 24 2015
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.