Getting Started With Go Part 7: Goroutines

This is part 7 in a series called Getting Started With Go. If you would like to check out parts 1-6 you may find them here:

  1. Hello World
  2. Variables & Constants
  3. Functions
  4. Arrays & Slices
  5. Pointers & Structs 
  6. Interfaces
  7. Goroutines (you are here now)

Most backend services nowadays typically need to do multiple things at the same time. This is called concurrency in software development. Each programming language has its own way of dealing with concurrency, Go’s way is something called goroutines. 

The good thing about goroutines is that they’re a native part of the Go language. Also, they’re not overly complex or hard to use. In fact, as cool as goroutines are on their own, they’re just one piece of the puzzle. They can be a lot more robust when combined with channels and WaitGroups, which we will explore in future blog posts. Let’s just stick to the goroutines themselves for now. 

In order to create a goroutine all it takes is two letters, go. Because goroutines are launched concurrently (when not part of a group a WaitGroup, etc.), they may not always finish their operations sequentially. Let’s write some code to illustrate this.

type RaceCar struct {
  Name string
}

func (r RaceCar) Race() {
  fmt.Println(r.Name, "just finished the race")
}

Here, I’ve made a simple RaceCar struct with a Race() function that simulates the car participating in the race. Pretty straightforward here. We’ll add one more struct that is a list (or slice) of RaceCars with a StartRace() function to simulate a full race. 

type RaceCarGroup struct {
  Cars []RaceCar
}

func (r RaceCarGroup) StartRace() {
  for _, car := range r.Cars {
     car.Race()
     time.Sleep(time.Second)
  }
}

Our StartRace() function is simplified for the purpose of this post. All we do is loop through the slice of RaceCars, call the Race() function, and pause code execution for one second to simulate time in the race. Inside our main function let’s start by declaring a RaceCarGroup with a few RaceCar objects and seeing what a regular race looks like.

func main() {

  rg := RaceCarGroup{
     Cars: []RaceCar{
        {Name: "lamborghini"},
        {Name: "ferrari"},
        {Name: "porsche"},
     },
  }
  rg.StartRace()
  fmt.Println("done")

}

If we run this, we should get the following results:

lamborghini just finished the race
ferrari just finished the race
porsche just finished the race
Done

Now let’s see what happens when we update the main function with a second group of cars to the race inside a go function.

func main() {

  rg := RaceCarGroup{
     Cars: []RaceCar{
        {Name: "lamborghini"},
        {Name: "ferrari"},
        {Name: "porsche"},
     },
  }

  rg2 := RaceCarGroup{
     Cars: []RaceCar{
        {Name: "tesla"},
        {Name: "bentley"},
        {Name: "corvette"},
     },
  }

  go rg.StartRace()

  go func() {
     fmt.Println("are we done yet?")
  }()

  rg2.StartRace()
  time.Sleep(time.Second)
  fmt.Println("done")
}

Starting off, we have our two groups defined. The first part of the race starts inside a goroutine, as we have used the go keyword. Next, we have defined another goroutine with an anonymous function. This is another way goroutines can be created as opposed to having an already defined function. Goroutines need to be followed by functions or else they will not work. Finally, we have started the second part of the race on the main thread, forgoing the go keyword. When we run this we get the following:

tesla just finished the race
bentley just finished the race
corvette just finished the race
lamborghini just finished the race
ferrari just finished the race
porsche just finished the race
are we done yet?
Done

Had we not used any goroutines like we did in the first example, you could expect the results to be printed differently. With the mix of print results in the example using goroutines, you can see the potential for using them in operations that require multiple smaller jobs to run at the same time before doing something else. 

As stated above, this is a small intro into one of the unique features of Go, called goroutines. In the following blog posts, we will take a look at channels and WaitGroups in order to use goroutines in a more robust manner. Let me know if there is anything else you would like to see. Any questions, comments, or any other feedback would be greatly appreciated. Thanks for reading!

Leave a Comment

Your email address will not be published. Required fields are marked *