Advanced Golang: Channels, Context and Interfaces Explained

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
what's up ladies and gentlemen it's ryan here and today i want to talk about three advanced concepts in golang so we're going to talk about channels context and interfaces these are tools that you can use to make your code cleaner and more maintainable especially as your project grows in complexity so let's start off with channels so channels are used when you want to transfer data between different go routines so what we can do is say data chan equals make chan int so what this does is it makes a channel of type int but this could technically be float or string or if we have our own data type here then we can even use that as well so with the channel we can do two things we can send data to the channel so here we're gonna send the integer 789 into the channel and then we can also receive data from that channel so we're going to say n equals the value inside the channel and then in the end we can print this and let's try to run this and see what happens okay so it's giving us a deadlock so why is it giving us a deadlock well we sent the value to the channel and then we received a value from the channel so the thing is by default a channel does not have any space inside of it you cannot temporarily store a value inside of a channel a channel is sort of like a portal so if you're gonna have data enter the channel it also has to be able to exit the channel simultaneously not before or after but literally at the same time so if i actually comment one of these out and i run it then it's still going to be a deadlock and if i comment this other logic out and then i run it okay it still turned out to be a deadlock so both of these lines actually have to execute simultaneously and the only way for that to happen is on different go routines right so if we do go funk so now we have them running on different go routines which means they can execute simultaneously so now we run it and yeah n equals seven eight nine everything works as expected so this is the thing about an unbuffered channel is that this channel has no space inside of it it's like a portal you have two parts of a portal the entrance and the exit and if you just have an entrance and you don't have a destination then it's just gonna block and it's gonna deadlock so in the end you have to have one thread that's adding data to the channel and then simultaneously you have to have one thread that's receiving data from the channel so let's go back to the old code for a second so this code if we run this code it fails due to a deadlock right because the channel has no space inside of it so technically you can actually make this into what's called a buffered channel where you can add space to it and now if i run this it runs fine so if you turn this into a buffered channel then you can temporarily store a limited number of values in that channel before you receive it but in this case i'm going to try to add two values and let's see what happens well it fails due to a deadlock on line 10 and it's failing because i only have space for one and yet i try to add two and so if i make this 2 now it works fine then we can copy this logic here to receive two values from the channel as well 7 8 9 and 1 2 3. so this is the most rudimentary example of what you can do with the channel but it's not necessarily the way channels are meant to be used channels are meant to be used in a multi-threaded context right it's meant to be used with different go routines so let's go over how that looks okay so now we're back to ground zero we have our data chan a channel of type int and it's unbuffered so what we can do is say for in and range data chan and i'm going to just print the value and this is another sentence you can use to grab values out of the channel which is pretty neat actually and if we try to run this of course it's going to deadlock because we're trying to receive data from the channel but we haven't sent data to the channel right the rule is if you want to receive data you have to simultaneously send data to the channel so funk and we're just going to have a loop here for each iteration of the loop we're going to add the value i into the data chain so it's going to go through the loop 1000 times and add the value i into the data chain and simultaneously we're going to consume data from the channel and then print it out here so this is running on a background thread while this is running on the main thread so if we go ahead and run this so if we scroll through it looks like it actually did go through all the iterations except at the end it said deadlock so what happened is it went to 999 it sent the data and then it went here to line 12 and it exited the go routine the go routine was done at this point there was nothing to do except here on the main go routine it was still trying to receive data even though there was nothing to receive and that put it in a state of a deadlock so all we have to do here is after we're done sending the data to the channel we do close data chan and if we run this again now it does all the iterations and it terminates fine without a deadlock so this is another way that we can use channels so let's go ahead and make this into a more realistic example so i'm going to have a function called do work and it's going to return an int and inside of it i'm going to do time dot sleep for one second and i'm going to have it return a random integer up to 100 so we're going to pretend that we're doing a whole bunch of hard work here but really it's just sleeping for one second and then returning an integer so what we're going to do here is on each iteration of the function i'm going to do result equals do work and then i'm going to add the result into the data chat now we're going to actually do some work so let's run it okay so what's happening here is it seems to be waiting one second for each one of these operations which is not what we want right the whole point is to go faster so what we want to do here is actually wrap this in a go routine so go funk so now it's a tiny bit more complicated because for each iteration of the loop it's going to create a new go routine and that's what we want but now we have to track whenever these go routines are done so that we can wait for them to complete and for that we're going to use a weight group on each iteration of the loop we're going to add one to the white group and then inside the go routine we're going to defer weight group dot done then in the end we're going to wait until all the iterations are done before we actually close the channel okay so now let's run it and see what happens okay so it seemed like it took about a second but then it went through all 1000 iterations and got it done so it's really cool about this is even though we had a thousand different go routines all of that data was sent to one channel and we were able to receive it at this one point of entry so this is an example of how you can use channels to manage the flow of data between your go routines so i've seen some very sophisticated examples of channels out on the web and on youtube and there's some really cool stuff you can do with channels but you also got to be careful because it's easy for it to get out of hand channels are really cool and fun to use but they're not always the best tool for the job sometimes a simple mutex will get the job done in a much simpler way but nonetheless it's good to know how to use channels because there are certain third-party libraries that use them and you will eventually find a time and place where it becomes a useful asset all right so now let's talk about context so context is a way for you to add a timeout or a cancellation to a go routine let's say you have some long expensive operation that takes 30 seconds to complete well you might want to add a way to be able to cancel that halfway through and context is one way for you to do that so here i have an example of some logic so here we create an http request it's going to make a request to this url now this url if i actually check it out so this is what comes up is just a placeholder image 2000 by 2000 and so that's what we're going to download with this code so we create the http request perform the http request and then we print the size and bytes of that data so pretty simple stuff that we have here let's go ahead and run the code and see what happens okay so says it downloaded the image of size about seven kilobytes so let's say we wanted to cancel this request if it takes longer than a certain amount of time well in that case we can use context to add a timeout to that request so we'll say timeout context equals context dot with timeout it wants a parent contact so we can just do context.background then it wants the duration so i'll say time dot millisecond times 100. so we'll give it 100 milliseconds and also it returns something called a cancel function and what we got to do is defer cancel so what this cancel function does is it actually cancels the timeout it's the equivalent of doing clear timeout in javascript so now instead of http dot new request we can do http dot new request with context and then pass the timeout context so a lot of libraries have this built in where they will accept whatever context you give it and that way you can add your own timeout or cancellation to it now let's run the code again and it says there's an error context deadline exceeded now if i bump this up to let's say 700 milliseconds okay now it's fine it's downloading within that amount of time if i bring it back to 50 milliseconds okay context deadline exceeded so we can see that it seems to be working that if it takes longer than this amount of time for the http request to finish then it will throw an error in cancel so this is one way that you can use context so here's another example of how you might want to use context so if you're using go to run a gen server then here's an example where i have a get request to the slash hello endpoint and i'm performing a lot of the same logic as before right new request with context this time i'm requesting the page for yahoo.com i'm going to grab all that html data and then pretty much send that back to the client then i'm going to go ahead and make the http request it took about three seconds but it did eventually come through with all of that html so one thing you'll notice here is i'm passing in context.requests.context and it says this returns the request context and it says for incoming server requests the context is cancelled when the client's connection closes so we can test this out send i'm going to cancel and we can see that it did actually throw an error it says context cancelled and so it immediately just canceled this entire request so that means that here it didn't waste any time doing any additional logic it just canceled the http request itself this is a great way to make your apps more efficient so that they're not doing more work than they need to so i can actually take advantage of the built-in request context but then i could add my own timeout on top of that so i could say timeout context equals context dot with timeout and here it wants a parent context so i could use the original request context as the parent context i could say times two and once cancel here so defer cancel then here we can use the timeout context so what we have here is we created our own context that is a wrapper around the request context with a timeout of two seconds and we pass that to our http request so now if we make a request i can see that after about two seconds that's when it threw an error and said hey context deadline exceeded so using this pattern is a way that we can preserve the original behavior of the request context and wrap that in our own worst case scenario timeout so if the request doesn't cancel itself within a certain amount of time then we can cancel it ourselves with our own timeout alright now let's talk about interfaces which is the easiest part and also my favorite part of golang so interfaces allow you to basically write a blueprint for how your app is going to behave if you're familiar with object oriented programming then you pretty much already know how interfaces work but because golang doesn't have like true object-oriented programming and inheritance and all that then interfaces gets you at least a step in that direction and it especially makes it easier for other engineers who want to jump in and understand what your services actually do so let's use a real world example in this case let's say we're creating a banking application so an app that allows you to manage multiple bank accounts but not just one type of bank account but multiple types of bank accounts let's say a user might have a wells fargo bank account a chase bank account and a bank of china bank account some international swiss bank accounts maybe even a bitcoin account and all of these are very different in terms of the apis that you would use to be able to interface with them and stuff but all of these accounts have a few things in common so i'm going to say type bank account interface so first i would want to get my balance and let's use integer let's say 100 equals one dollar so this represents cents and i know i want to be able to deposit money into my account so a certain amount and i know i want to be able to withdraw a certain amount as well and this might throw an error because i might withdraw too much so these three methods pretty much represent what any bank account can do whether it's wells fargo chase bitcoin this or that gonna be able to talk to all of these bank accounts by using the same interface so now let's create different types of bank accounts so first i'm going to create a wells fargo bank account type wells fargo and depending on the ide that you're using you might have some functionality like this where where your ide will automatically fill in these methods for you and so that makes it a lot easier so now this wells fargo struct implements the interface for bank account because it has all of the matching methods so i know i'm going to have a balance here that's an int i'm also gonna create a constructor for this so got a balance we got a constructor that initializes this and now we can add the actual implementation for what it means to get a balance or deposit or withdraw so first i'm going to change these to pointer receivers because i am going to modify the underlying data for getting the balance all we have to do is literally say return w dot balance so for the deposit we're just going to say w dot balance plus equals the amount so for withdrawing we need to be a little bit careful because we don't want to allow the user to withdraw too much unless their name is ryan right so we want to say new balance equals the old balance minus the amount and if the new balance is less than zero then we're going to return an error here insufficient funds i guess that's what they say nowadays otherwise we do dot balance equals new balance and and return new because there is no error so now we have fully implemented a wells fargo bank account so here at the main function i went ahead and instantiated a new wells fargo account i deposited 200 into that i grabbed the current balance and then printed the balance so if we go ahead and run this it says wf balance is 200 cool and let's add a little more chaos to this let's say 100 370 and we'll do a withdrawal at the end of 117 we should technically acknowledge the error for this and now let's run it and see what happens so on the end it says the balance is 553 so it looks like everything's working okay so this is cool i have one type of bank account that fits this interface but that doesn't really show the true power of interfaces so i'm going to actually create another type of account called btc and this is going to be bitcoin and i'm going to say type bitcoin account struct and like before it has a balance which is an integer and like before i'm going to let my ide auto generate these methods for me and also change these to pointer receivers and same situation return b dot balance to deposit i'm going to say b dot balance plus equals the amount and with the draw i'm going to copy and paste that original logic from wells fargo now we have a bitcoin account i'm going to go ahead and add an initializer so i just added an initializer to create a new bitcoin account and have a balance of zero to start off with and for this one i actually want to make it a little bit different i want to add a fee so we're going to say that the fee is let's say 300 which is 300 cents that's gonna be three dollars and anytime you want to withdraw money it's gonna be the original balance minus the amount minus the fee as well so if we compare the wells fargo account here the withdrawal we only withdraw the amount but then for the bitcoin account we're going to withdraw both the amount and the fee so these two different accounts are very similar in what they do but there's just one little difference in implementation so this is where the power of the interfaces really comes in so i could say my counts is an array of bank account and just for a little bit of clarity you might want to use a prefix and call this i bank account just to be clear that it is an interface my account is an array of i bank account and i'm going to add a new wells fargo here and a new bitcoin account cool and now i can iterate over this i'm going to say for account and range my accounts account dot get balance say balance equals get balance and then i want to print f so what's cool about this is here's a list of all my accounts and as far as typing is concerned we're using the interface as the type and this allows us to treat all of these as pretty much the same thing so i can iterate over all of these accounts get the balance for it and then print that out and if i do that it says bound to zero bounce is zero of course so let's mix it up a little bit i'm going to do account dot deposit 500 and then account dot withdraw 70. and if there's an error for the withdrawal then i'm going to print the error okay so let's go ahead and take a look at this okay interesting so it's saying the balance for my first account is 430 and the balance for my second account is 130 and so you can see that we're performing the same operations against all these accounts and these are treated as the i bank account type but because these bank accounts behave a little bit differently in their implementation that's the reason that i'm getting a different balance here so what's nice about this is if another developer comes in and sees this then they know exactly all the things that an ibank account can do and also the future you that looks at this code in three months or three years later it makes it easy to jump back on board and quickly understand what the code is doing alright so that's it for today ladies and gentlemen i talked about channels context and interfaces these are three advanced concepts in go that will level up your code make your code a bit more clearer and easier to maintain as your projects grow in complexity so hey if you like the video please like and subscribe and support a little youtuber like myself thank you for watching
Info
Channel: Code With Ryan
Views: 46,885
Rating: undefined out of 5
Keywords:
Id: VkGQFFl66X4
Channel Id: undefined
Length: 22min 17sec (1337 seconds)
Published: Sat Sep 25 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.