Transcript from the "Concurrency Definition & Practice" Lesson
>> The next thing I want to talk about, to kind of close out some of these code along examples is the idea of concurrency and multitasking in Go. So Go has a tool called a Goroutine. And a Goroutine is a super lightweight thread managed by the Go runtime that allows Go to asynchronously fire off a lot of different tasks at the same time in a more streamlined way.
[00:00:23] So to implement a Goroutine, you simply put the keyword, go, before the function you'd like to execute. And go behind the scenes knows it's going to spin up a separate thread to make something happen. So in the example on the screen here we have a function that is apparently going to do something incredibly slowly.
[00:00:39] And then in our main function, we're setting that to a Goroutine. So go can continue execution with the rest of the program while the slow thing happens. So we're gonna code along a real quick example of what this looks like. Concurrency in Go is an incredibly powerful tool, and I think there's a ton to it.
[00:00:59] So the goal for this exercise is to kind of just demonstrate how painless it is to kind of start spinning up some asynchronous functionality. We won't go too far into what's actually happening behind the scenes. That would be a really great next step to kind of talk about.
[00:01:12] So let's hop over to Concurrency.Go in our Chapter 13, and then take a look at Goroutines. So in the concurrency dot Go file, I have some functionality that already lives here. In our main function we are calling the save function twice. The first time we're saying hello the second time, we're saying there and if you take a look at what's happening in the save function it's expecting a string and then for an iteration of three iterations.
[00:01:41] We're going to print that string and then sleep for 300 milliseconds. So if I fire this off right now We are in chapter 13 and go and concurrency. You'll see that each of those things prints but it takes a minute before it happens. So let's start with some Goroutines, and look at what happens when we tell Go to start handling this in a more multi-threaded way.
[00:02:22] So in our main function, the first thing that's gonna happen, slowly, is this guy. So we can throw the word go before say. Let's fire it off again. And notice that already we're seeing some a little bit quicker where as something's taken a minute it's firing off the next iteration.
[00:02:40] It's firing off that next execution of line 17 of say there. We can also put a thread before say there, and watch it happen a little faster. Or not at all. So what's interesting about Goroutines is they allow the code to be non-blocking. If every single function is set to a Goroutine, nothing is going to execute before the Goroutine has finished execution.
[00:03:15] So right now we're saying like, hey, spin up another thread, let something else finish first. But we are doing that twice. So spin up another thread. Let the next thing finish first. The next thing is like cool, spin up another thread. Let whatever happens next finish first. And because there's nothing else happening, nothing actually finishes execution the way we expect it to.
[00:03:34] So we'll remove that second go, and we're back to seeing all of our execution. So if we do wanna actually manage this more specifically, we can use a package that exists called a sync. And sync is gonna be a way that we can kind of synchronize all of our asynchronous functionality.
[00:03:55] So the package is sync. Up here at the top. So what sync is gonna do is it's going to ask for what order do we wanna do things in? And kinda it needs more instruction to help us manage all the functionality, what we wanna happen, when, and how.
[00:04:14] So the first thing we need to do to actually use the sync library is establish something called a WaitGroup. So I'm gonna set a variable called w g to indicate WaitGroup. And then use this thing package to instantiate a WaitGroup, which will be the type of this variable.
[00:04:31] Cool. We're gonna add some new functionality. I'm gonna kill the Save function for now we're gonna do something a little bit more involved. So within our main function here, Let's say we have a function called printStuff, which means that above our main function, I'm also going to create a printStuff function.
[00:05:00] Within printStuff, we're gonna have kind of a similar four loop from our previous example set the initial value to zero. Increment up to about three and then add one each time. We're gonna go ahead and put in just function. Let's print out whatever index we're on. And then let's add a sleep timer.
[00:05:26] Time.Millisecond times however long you'd like to wait. If you haven't imported your time library, my code editor is automatically adding these for me, so make sure you have both time, fmt, and sync imported at the top of your file here. Cool. And if I run my code now, I just see 0, 1, 2 print real slow.
[00:06:01] If we make this a Goroutine, and we run it, we get back to that situation where we're not seeing anything print out. Our Goroutines, spinning up a thread and then it's cool, nothing's blocking and Go kind of shuts down. So what we can do here is we can use our WaitGroup, And save waitgroup.add.
[00:06:24] And so WaitGroup is gonna take the add function, and we're gonna hand it an integer. It's gonna add it kind of behind the scenes to a queue and be like, okay, what are the things that we want to accomplish? And how do I know when these things are done so I can actually execute the code?
[00:06:39] So we can instantiate our WaitGroup. And then after we're done running whatever code we want and however many threads we have, we can tell WaitGroup that this is what we're waiting for. We need to actually put a blocker so that Go will continue execution. And so if everything is good once all of the counters have been released.
[00:06:57] Our WaitGroup is complete. And so besides just having our counters being incremented, behind the scenes in our WaitGroup, and something saying like, hey, wait until this is done, we need to add an additional line within our printStuff function that tells our WaitGroup, okay, you're cool things are done.
[00:07:12] Everything's good to go. So we're gonna use defer our fancy method defer here we're going to say, hey, WaitGroup You can be done. Okay, so we have defined our WaitGroup. We've got some functionality to make sure our asynchronous code is behaving the way we want it to. So our waitgroup.wait is gonna wait until all of our counters have been kind of popped off the stack.
[00:07:38] And so waitgroup that done here is a way for us to be like okay, pull one off the stack, but if something goes wrong, our waitgroup.wait is going to kind of hang out here for quite a while. So let's add one last kind of error handling check. We're gonna use defer again and we're gonna create a function called handle panic.
[00:07:58] Similar to when we were talking about r defer and recover functionality. And so up here let's create a func called handle panic. And so we will use our recover function. Cool. And so we're saying that, okay, if recover has been called because there's been a panic and defer helped us get here, we need to do something about it.
[00:08:26] We're just gonna let the user know, That there's panic. This won't modify any of the actual functionality because everything's working great. But the important part to note is that because our WaitGroup is waiting for all of those counters to be popped off the stack here if something goes wrong and we don't handle it the asynchronous will just like kinda keep hang out and we won't see the execution we expect.
[00:08:59] So next steps if we were to keep building this, we would start talking about the concept of a channel. So right now our functionality in printStuff, all we're doing is printing to the screen. We're not returning anything and we're not trying to do anything with that information. But in the event we actually wanted our Goroutines to be able to communicate to each other we would do something kind of channel and channel allows you to open up a channel of communication between two different Goroutines, pass information back and forth and do a little bit more complex logic.