Goroutines Crash Course (Mutex, Channels, Wait Group, & More!)

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
so have you ever wanted to 10x the speedier app well this probably won't do that but it will probably increase the speed of your app probably disclaimer disclaimer you know you have a certain use case it might not do it blah blah blah you get the picture but today today we're going to be talking about concurrency parallelism and go routines one of my favorite features of the go language and something that I think not nearly enough people understand and utilize so today I want to break down in simple terms how all this works how to use it how to implement it give you some examples get you on the right path and yeah before we get into that real quick make sure you guys are subscribed I'm gonna have new stuff for go land coming out every single day for the next uh I think it's like 40 something days all the way through February 28th make sure you're subscribed make sure you if you like the video you do so and uh yeah with that said let's start breaking down concurrency parallelism and go routines so I think the best place to start with all of this is with a very visual example so first and foremost what does a go routine do a go routine is going to spin out a separate process from your original one so if we imagine we have right here we have our main process running here so when we start up we execute Funk main this VAR WG is going to happen right up here and then we're going to add on one so we're all just executing down the line down the line of our program but then when we hit this go routine right here we're going to spin off another arrow that's going to be running over here so we're going to spin up for this for this go Funk right here we're going to spin up another thing running right here and then when we go back down here this so then this will be executing at the same time as the rest of this so these two start running in parallel or concurrently we'll talk about the difference in a moment here so we spit off another instance which is going to be running here and then once we go down here this WG dot add one is going to be happening on this original thread then we then when we hit this go Funk again we're going to spin off another one and we're going to be running it here so these are going to be running but the main program is going to be on this one so then when this one terminates the whole thing is going to terminate these are just going to be left dangling and they're just going to disappear into the void so we need to make sure that we have a blocker here so this WG Dot weight is actually a blocker so what this is going to do is this is going to keep a block right here so it's going to block until those two are done and we've gotten the signal that both of these are done but that's a very high level view of what a go routine is so first I want to talk about what is concurrency and what is parallelism so we talked about before this is what's really happening worse we have this one main thread and then we're spinning off more threads that are going to be happening at the same time but in reality they're not necessarily going to be happening at the same time so there's two ways this can happen the first way is going to be if it's running concurrently so concurrency basically means that we're going to have multiple threads or processes running in they're going to be interleaved with each other and they're going to be happening at the same time you can imagine if I'm sitting here trying to cook something if I have if I need to cut up vegetables and I need to cut up meat I'm only going to be able to do one of those two at the time but if I go ahead and I cut up half the meat that I cut up half the vegetables and I go back and cut up half the meat then cut up the other half of the vegetables that's concurrency I'm doing two different processes they're starting and stopping over lapping with each other so a visual example this would be we get down in here and I'm going to set this to be let's make this teal so this group right here I'm going to put a box around this right here so this group right here is my first group and then I'm going to make another box over here around this group right here so these two functions are going to be executed differently based on whether this is concurrency or parallelism so in a concurrent World we're going to have one stream of executions we have access to one thread so what we can imagine is happening here is we have like maybe our green is going to run for a little bit and then that's going to get intermixed with our teal here or I guess that changed the color I'm dumb so you get the idea so we have our teal here and then we're going to have some green running next so then green will run for a little bit and then maybe after this we're going to get some more uh teal running and then maybe this will run for a while and then it'll complete and then finally we need to run this for a short period more and then finally that will complete we'll go back to main so these two are getting mixed in together different lines are happening at different times we could have this defer happen and then three iterations of this happen and then this happens completely indeterminate we have no idea what order they're going to happen if and we can't rely on the order we can't rely on this happening before that which is something that you're going to get into in more advanced concurrent topics but we need to make sure that these are synchronized properly but then in a parallel environment this is going to happen very differently just realize this is a different color green let me fix that so in a parallel environment this is actually going to be happening differently we can imagine that these will instead of have being stacked on top of each other these are going to be happening next to each other so this will be running here and then right next to it this will be running in parallel so this will be happening on a different CPU core so on another CPU core we're going to have these two running at the exact same time the way you actually do this is by setting a variable within go called go Max prox that's going to basically Define how many different CPU cores we can use by default go we'll just read however many it has access to and set that for you so you don't actually have to do anything to set this up you have access to multiple cores go will go ahead and do it parallel we only have one it'll use concurrency so it just depends on what you have access to so you don't really have to worry about this but this is just a conceptual thing that you want to make sure that you understand is that it can either be parallel or concurrent so with all that out of the way let's sort of talk about the key Concepts that are happening here so with those sort of Concepts out of the way let me show you this code actually running so if I go over here I have this pulled up and if I just do go run Main dot go you're going to notice that these are happening we're getting first and second mixed into each other here on my computer this is a 12 core machine I believe so these are running in parallel but it's indeterminate which one's going to go at what time because it's just this random sleep but you can see these two are happening at the same time they're being mixed together because they're running in a separate thing but our main process is being blocked by this weight group so with this sort of basic example out of the way hopefully you get the idea of what a go routine is let's talk about some of the more key Concepts here and the sort of things you're going to need to actually use this in real life so the first thing I want to talk about is a weight group and that's something that you're already seeing being used here but a weight group is basically just a way for us to block the execution on our main thread until we're done using all of our concurrent uh channels this would be very useful say for example this we're an API route and we wanted to go ahead and fetch from two different external sources at the exact same time we would just have a weight group here that would wait for both of them to be fetched we would do all of our Logic on the results down here and then send it down to the user I actually use this with an Insider viz in a couple different ways cases this makes it really easy to do simultaneous API calls so going back to our example over here what the weight group is actually doing under the hood is it's actually just set it has a little internal counter is the best way I like to think of it and that counter is going to be incremented or decremented based on the status of our actual um based on the status of our current go routines so if we set this over here I can just say add the initialization WG is going to have a counter of zero so up here we have a counter of zero then after we add one our counter goes up to one then we go ahead and we defer weight group dot done but this but here we're putting the defer keyword in here so it's only gonna it's only going to set WG to done when we are actually when we complete this method and this method terminates and this method isn't going to terminate until this for Loop completes so this defer WG dot done is going to happen at the end of this go routine and what does WG dot done is going to do is it's going to decrement our weight group down by one so here I'm just going to leave this at one for now because this has to execute and pair parallel then we're going to go down over here and I'm going to say WG is now going to be equal to 2 because we're adding another one we can go ahead and we can say that this is still going to be going and then finally down here at the end we can say that our two go routines have completed it's deferred it's down to zero and once it's zero this WG dot weight will stop blocking this will just block until this is not zero anymore and once it's zero it's going to go ahead and print out done and then we can continue on the way it does this is because it has a shared memory reference that it's going to be pointing to and we can pass that data back and forth between our channels so with all that said we can run these two things in parallel how do we get data in and out of these go routines and the answer to that is channels what is a channel a channel is one of the most useful things in golang's concurrency and it is what allows us to pass messages back and forth between different go routines it is effectively the way I like to think of it is a sort of pipeline it's a pipeline that we can use to send data from one place to another between these go routines so remember our example earlier where I said we have our main thread here so our main thread is going to be running down here and on our main thread we are going to go ahead and we're going to create this channel C so this channel C is going to be created somewhere in memory so I'm going to create this over here to represent our memory and I'm going to call this guy right here this is going to be C so this is our Channel c c is going to exist over here and it's going to be empty at the start of our program's execution and that's going to be created right about here and then we're going to spin up this go function here and we're going to be using C within here so what's nice about go is it can just encapsulate variables so we just pass variables into an anonymous function even though we're spinning off another thread here so our go function here is going to create another thread over here that's going to execute but if we pass any variables it here those are also going to get passed in here so we can use them in there it makes it really nice you don't really it's a lot easier to deal with passing variables back and forth between go routines and stuff and functions in go than it is in something like C this is not going to get into this video is not going to get into stuff like scoping and all that stuff that is Way Beyond the scope of this but just know that if we pass C into if we just use C within this Anonymous function it'll be used correctly in scope correctly so no issues there but now with that out of the way we have these two different threads that are running and we have our C down here so within this uh go Funk right here what we're going to do is we're going to go ahead and we're going to run this for Loop so all we're doing is we're just summing over I so from 0 to 100 we're summing nothing or I guess you're at a 99 so zero to 99 or something nothing special here and then down here we are going to do some pointing to C so what is that doing that's going to take this sum variable and it's going to put it into C so that means that from this thread over here we're going to take our end value here and that's going to get pointed into C we can imagine at this point in the program we're going to be loading into this channel we're going to be loading the number whatever that sum is it's like some number we'll just say we're loading some into here so our sum variable is getting loaded into C and then in this main program in this main thread over here we're going to read out the results of that we're going to read that out into here so we're going to put that in there this is beautiful drawing by the way so we're reading that out there so we're going to take it we're going to put it in here and we're going to pull it out there and then we can print out that output and this is a blocking method so this right here is going to block execution until it's complete so until we get something out of this channel it's going to block so that's what makes the synchronization on this super easy is we can just run this for Loop over and over and over again and this will not just terminate out of nowhere without this so if I go over here and I just do go over on main.go it's going to say idx from each one and I'll say output is this and that is just some getting pointed into here what's interesting is if I remove this right here if I remove all this stuff it's not going to print out all this if I do go run main.go it's just it's not going to print anything out because we're not blocking we're not waiting for this other one so this is just going to die and Float out in the void so we need to make sure that we're pulling out of this Channel and then we're doing that that's a very basic example of what you can do with channels but effectively just think of them as ways to pass messages between different threads and different go routines it makes it super easy to synchronize them and keep data going back and forth there are many there are more advanced concepts on these if you want to see a deep dive on channels let me know but this is the sort of introduction is just think of them as this little Block in memory over here that we can put data in and pull data out at will and the last thing I want to talk about today is going to be mutex so a mutex or a mutual exclusion is a way for us to ensure that one place that like one that if we have a spot in memory that spot in memory is only going to be written to by one thing and why is this important so remember earlier we have multiple things running here so in this case I'm actually going to be spawning off 100 go routines right off the rip so this is going to be there's going to be 100 just use the two for now but imagine we have these two threads running in parallel and we have this over here and this what we're going to be updating is going to be this map of Key so that map of key is going to exist somewhere in memory so we can think of our memory as this over here this is a terrible little Recreation of memory but you get the idea it looks something like this and imagine right in here we have our memory we have whatever this s dot num map key is going to be imagine it's right here and imagine we have all of these different threads we have a hundred of these and they're all trying to write to this one little spot all at the same time it's gonna crash so what we need to do is we need to create a mutex which is a mutual exclusion which will make us that only one of them can write to it or read from it at once so the way we do that is we can go ahead just create this safe counter thing so all this is going to do is this is just going to create a mutex and a map so then within this map I can go ahead and say okay within this method so I'm adding a struct method to this there'll be another video on those in the future but this in the struct method what I'm doing is I'm just going to go ahead and I'm going to lock the mutex so mutex has two methods as a lock and an unlock a lock will make it so that anytime you try and access this you can't do it and then unlock will make it so that anything else can now access it so one of these guys will go in one of these threads will get there first and when it gets there first it's gonna lock it so when it locks it all the other ones are going to be blocked on this lock they're not going to be able to do anything until it unlocks so it'll sit there and wait on the other ones and then that one will be able to go through and they'll be able to write to it so it'll just set it to equal to whatever value pass in here it's a very dumb example you wouldn't do something like this in real life but this is just to illustrate the sort of Point here of writing only one thing to memory at a time so we go ahead we do that and then we go ahead and we defer s dot mu dot unlock and like I said unlock we'll free it and allow the others to actually mess with it and edit it so what we can do here is we set up our save counter we initialize a new map in here I'm going to create a weight group to wait for all of these to go through and do their thing then I'm just going to call this go routine down here which is going to try to add to all of them at the exact same time and then at the end of this whatever value is left in here is just the one that got to it last so if we go ahead and run this in here we go ahead and do that we're gonna get 99 it was the last one if we run this again we'll probably get a different number um well we're getting lucky here with 99 oh 83 there we go so we just keep running this over and over again we're probably going to get different numbers every time so the whole point of this is that it's indeterminate we don't know which order they're going to run in but if we went up here and we got rid of our lock and we didn't lock to it and we do go run main.go it's going to explode and the reason it explodes is if we scroll all the way up here it tried to do it like a million times so it crashed but if we go here it's going to say concurrent map writes so we're trying to write to a map with the we're trying to write to a map at the same time we're taking two different methods are trying to write to it at the exact same time and then it breaks because that reference is going to get broken so we have to make sure that we only have one writing to it at a time the way we do that is we accidentally close our editor but I did save it so if I just do go over on main.go it's fixed so with all that said hopefully that gives you a better understanding of concurrency gives you a good introduction on this this is just a very preliminary sort of Crash Course on how all this stuff works but I think that with these sort of skills you can go ahead and actually do some really useful stuff there's one of these topics you want to see broken down more let me know and uh that's about it thanks for watching have a great day
Info
Channel: Ben Davis (Davis Media)
Views: 19,067
Rating: undefined out of 5
Keywords: Go, Golang, Concurrency, Parallelism, Programming, Web Development, Software
Id: 5Z8skvm4g64
Channel Id: undefined
Length: 15min 42sec (942 seconds)
Published: Sat Jan 14 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.