Welcome everyone to part 18 of the Go Language programming tutorial series, where we'll be talking about concurrency in Go. First, let's acknowledge what concurrency is, and what it isn't. Concurrency is dealing with multiple things at once, and it is not actually doing multiple things simultaneously (parallelism).
Let's consider an example of a CEO who is working on writing a company memo, but is also answering emails as they come in. This CEO is working concurrently. They're writing the memo, and then, when a bell goes off that they got an email, they finish the sentence they were working on in the memo, pause their work on the memo and shift over to complete the email task. Then they bop back over and pick up where they left off on the memo. They're concurrently doing emails and the memo, but they're not doing emails and the memo simultaneously in parallel.
Great, let's try this in Go! A Go Routine is a lightweight thread, which we can easily spawn by simply prefacing the function call with go
. For now, let's just use a basic hello world example:
package main import ( "fmt" "time" ) func say(s string) { for i:=0; i < 3; i++ { time.Sleep(100*time.Millisecond) fmt.Println(s) } } func main() { go say("Hey") say("there") }
In the above case, the say("hey")
is being run as a goroutine
, but say("there")
is just a regular function call. If we run this, output is likely:
there Hey there Hey there
One thing to note about these goroutines is that they aren't going to necessarily run or complete before your program ends. For example, what if we changed our main
function to:
func main() { say("Hey") go say("there") }
In this case, the say("Hey")
runs first, but it's not a goroutine
, so it's going to run in full first, hogging resources for itself first. Unfortunately, after this, the only other line of code is for the goroutine to run, which doesn't really have any priority to actually keep the program overall running, so the program is done. What might happen if we did:
func main() { go say("Hey") go say("there") }
... Nothing at all!
But, if we just add a sleep at the end:
func main() { go say("Hey") go say("there") time.Sleep(1000*time.Millisecond) }
Then we get something like:
Hey there Hey there Hey there
Very cool, but this only works if we know the exact time that our goroutines will take, or something close. If we're too high in our estimation, we waste time, if we're too low, our processes don't run! Neither of these are good. There are some hacky ways we could solve this, but, chances are, the creators of Go probably have thought of this! ...and they have. We'll be talking in the next tutorial about how we can make certain our processes are complete before continuing along.