Introduction to Concurrency in Go: Gopher Farm

Olena Stoliarova
4 min readApr 11, 2019

Concurrency can be very tricky for beginners. Reading articles about gophers loading ore and looking at animations that represent goroutines is what helped me a lot when I started learning Go. However, there is a gap between these beginner-friendly articles and real-world projects where you often cannot use familiar analogs. I totally agree that practice is the best way to learn things and after you read some theory you should try new things out yourself. That is why I adore programming kata. Today I want to describe a step-by-step solution to a very funny one I had to solve during a job interview.

The task is as follows: you receive a JSON string that represents a farm where gophers live (sleep and eat actually) and the amount of food in units (perhaps grams as gophers are small animals) these gophers have in the barn. As no more food grows on the farm, our gophers will die one by one when they have nothing to eat. If this is too sad for you, feel free to code Chapter 2 where food trucks come to the farm and gohpers breed.

First, we need to turn inputJson into a structure we will use later. Each element in the “gophers” array has the same structure. Let’s turn it into a type Gopher. It’s up to you how to name fields but it’s better to name them after JSON fields for better code readability. JSON tags after field declarations are optional but I strongly recommend to have them in case you have a different order of fields of the same type in the structs you are going to unmarshal. Check that struct fields are exported (e.g. start with a capital letter) because standard Unmarshal function from json package cannot change values of unexported fields. The reason is that in Go visibility is restricted to a current package only if a struct, variable, function, etc. is unexported. Such unexported objects can be still changed from outside but you need to write special methods for this. Type Farm represents the whole inputJson. We will put type declarations outside of the main function to access them from other functions.

json.Unmarshal accepts a []byte and a struct we want to fill with values. So let’s initialize a variable gopherFarm of type Farm and convert inputJson to []byte. It is a very good practice to log errors because they help to debug if something goes wrong. There are many custom Go loggers used in real-world applications but for this small kata, log package is enough.

Gophers come to the farm and will live there independently of each other. It means that we need separate goroutines for each gopher and (obviously) gophers should be able to live (gopherLive function). Let’s populate the farm and write a boilerplate for a gopher lifecycle.

The next and the most challenging task is to make farm food available to gophers. Here are the new eatFood function and updated gopherLive function.

We can run this code (add time.Sleep(time.Second*20)) at the end of the main function to see log output. Probably, everything will be fine… or won’t. The problem with this code is that it may happen that two or more gophers (goroutines) attempt to read and change the amount of food on the farm at the same time. This condition is called a data race. To check for a data race, simply run the application with -race flag:

$ go run -race main.go

You will see the similar output:

Here comes a cool feature from sync package: Mutex, mutual exclusion lock. It will prevent simultaneous read/write operations for Farm from multiple gophers. We will add a field of type sync.Mutex to the Farm type and lock it when a gopher looks if there is some food left. Note that we use defer. Defer is a Go way to tell that the function must be executed before the end of the surrounding function and it is executed after the execution of return. Defer will be executed even if this function panics.

We are almost done! A final touch is to add more information about gophers’ life because we also want to know when a gopher dies. We will make a channel messages that accepts strings and send a message to this cannel every time a gopher dies before stopping (return) of a corresponding goroutine. Now we can read from this channel and until all goroutines have sent a message.

You can find the full code in my GitHub repository or run it on Go Playground.

I hope that it was helpful and interesting. Be creative and keep practicing!

--

--