Skip to main content

Our first Goroutines

You already met Pam in the previous chapter. Today we'll introduce you to Jim. Jim is quite similar to Pam: he also does his job in 2 seconds.

Let's put them to work in the main goroutine and wait for their results:

package main

import (
"fmt"
"time"
)

func main() {
start := time.Now()

worker("Pam")
worker("Jim")

fmt.Printf("\nall work was done in %s seconds", time.Since(start))
}

func worker(name string) {
fmt.Printf("%s: doing my work...\n", name)
time.Sleep(2 * time.Second)
fmt.Printf("%s: done\n", name)
}

See it in Go playground

Running this example we see that the total work was done in 4 seconds. The work was done in synchronous order, first Pam then Jim. But in real life we don't want the work to be done sequentially, rather we want the work to be done a concurrent mode.

We do this with the go statement. go worker() starts a new goroutine for the worker function. Let's add the go statement and see what happens:

package main

import (
"fmt"
"time"
)

func main() {
start := time.Now()

go worker("Pam")
go worker("Jim")

fmt.Printf("\nall work was done in %s seconds", time.Since(start))
}

func worker(name string) {
fmt.Printf("%s: doing my work...\n", name)
time.Sleep(2 * time.Second)
fmt.Printf("%s: done\n", name)
}

See it in playground

Output:

all work was done in 0s seconds

Running this example we see that there is not output from both Pam and Jim and the main goroutine exited with no work being tracked. What happened?

Well, you told Pam and Jim to go work in their office, and after you did that, you just exited the office and went home without waiting for their results.

Now all goroutines are running at the same time, including main. main doesn't sleep for 2 seconds: it launches the two goroutines and exits. When main exits, the program is finished, so we won't see the output from the launched goroutines.

Now, this doesn't make sense: if we give someone a task, then is just normal to wait for them to finish their work. So let's do this by sleeping the main goroutine for 3 seconds to make sure we are getting the output from the workers:

package main

import (
"fmt"
"time"
)

func main() {
start := time.Now()

go worker("Pam")
go worker("Jim")

time.Sleep(3 * time.Second) // let's wait for Pam and Jim

fmt.Printf("\nall work was done in %s seconds", time.Since(start))
}

func worker(name string) {
fmt.Printf("%s: doing my work...\n", name)
time.Sleep(2 * time.Second)
fmt.Printf("%s: done\n", name)
}

See it in playground

Output:

Pam: doing my work...
Jim: doing my work...
Jim: done
Pam: done

all work was done in 3s seconds

Now we see the output from Pam and Jim because we are waiting 3 seconds for them. The work is done in less than 4s as in our first example because now Pam and Jim are working in a concurrent mode.

Running this example multiple times you will notice that sometimes Pam starts work before Jim, sometimes the other way around. The reason for that we will cover in our next chapter, but if you think about it, if you tell 2 workers to do some work, you don't know which one will start working first. Maybe Jim goes to grab a coup of coffee and starts working after Pam. Your job as your boss was just to tell them to go work, the rest is up to them.

In future chapters we will see how you can properly wait for them. The time.Sleep is really not an elegant solution because you don't really know how long the goroutines will last. You want to wait until they are all done, not a second more.

In the meanwhile you can go grab yourself a coup of coffee, after all you deserve it: you're the best boss!

Notes

  • Normal function execution is done in a synchronous order. To run them asynchronously or concurrent mode, we launch them with the go statement.
  • When we start some goroutines we need to wait for them to finish their work. Otherwise main exits before waiting for a result.