Golang Goroutines 101

SumUp employee working on a video call

Written by Vitor Falcao

Golang is famous for its concurrency management. I'll describe and give examples of how to use one of its most powerful features: goroutines.

At SumUp, we've started using Golang in our daily lives. It's an incredible language to write services and tools. After I began using goroutines, everything became a lot faster, without dropping safety.

Since this is a basic approach, I'll start showing you how to create goroutines. It's straightforward and requires only a simple keyword: go.

Creating Goroutines

The code below creates a goroutine and executes asyncFunc . We have to add a time.Sleep after because the main function returns before the goroutine's ending.

But what if our goroutine ends in less than a second? We don't want to waste time running the program if it's ready to finish. It's time to use sync.WaitGroup.

Waiting for the Goroutine

Golang has a fantastic feature, which is WaitGroup. Fortunately, when we check its official documentation, it has only three functions: Add , Wait , and Done . Let's learn by example.

We create a WaitGroup which wait for one or more goroutines to finish. You must call Add with a delta to set how many goroutines it will wait to complete. Therefore, you also must tell it when one of them ends, so we invoke the Done function.

After creating every goroutine we need, we wait for them to complete using the Wait function. Easy, right? No more time.Sleep in our code.

Communication between Goroutines

Asynchronous would be useless, or at least way less powerful, if they couldn't communicate and exchange data, so it's why we have channels.

In the example above, we've created a channel with ten integers capacity. Then, we passed it to the receiver and the sender function. They are both in different goroutines and communicating. The channel acts as an asynchronous and thread-safe queue.

We must comment on two corner cases: when the channel is empty and when it's full. In both cases, the program waits for the condition to change. So, for example, if the receiver tries to read from the channel and has nothing, it waits until another goroutine adds something. The same happens when you try to add to a channel with reached capacity.

Simulating a store

Let's have some fun. We will pretend we have a store, and customers make purchases every 100 to 200 milliseconds, but it takes between 300 and 400 milliseconds for our system to process each.

Can you see the problem? If we have one goroutine to take care of the orders' queue, it will grow infinitely, and our customers will get mad. So instead, we should create more goroutines! Four of them should be enough.

Now we're wasting resources! Most of the time, we keep our queue clean, with no orders to process. Let's suppose we have an agreement with our customers to process it quickly, but they could wait for a second or two. How can we guarantee this queue won't be empty and neither full?

And here we have it! Now we're autoscaling. We could improve this code significantly, but it's an excellent beginning. Also, it's a common problem to solve in interviews for experienced Golang developers.

Thanks and have fun simulating stores.