Golang, goroutines and channels

Sagar Saini
3 min readMar 17, 2022

The go-routines and channels play a major role in concurrency and it is also a major selling point of Golang. I hope you have come here after having a good understanding on how Golang works. Let’s get started now :)

What are Go-routines?

Go-routines are the lightweight threads of execution. It normally works independent of the code if called independently but this can also works simultaneously if connected to other go-routines or a piece of code. The syntax is very simple to create a go-routine from an existing code. You just need to append the keyword go in front of the function you want to be used as a go-routine or you can make an anonymous go-routine as well. But we’ll use the former approach in this example. Like: go sum(arr []int)

In this example, we’ll see how go-routines work with channels without using waitgroups. Now, you would be wondering what a channel is? Let’s see what a channel is!

What are channels?

You can term channels as the pipelines through which you can connect concurrently running go-routines. You can send data (any type) into the channels from one go-routine like done <- true and read that same data in another go-routine like status <- done. I mean how cool is that? right! Let’s see more on that further. But before that let’s get to know about the types of channels we have. We have two types of channels in Golang: buffered and unbuffered channels.
Buffered channels: These are the channels where we can provide the maximum size of the data it can hold at a time.
Example: done := make(chan bool, 1)
The above example depicts that the channel done expecting boolean values can have only one value at a time. Second value will only be able to go into the channel once the first one is taken out of it. If we try to push another value without poping the first one then it will result in deadlock and we do not want that.
Unbuffered channels: These channels don’t have maximum size associated to them but we need a listener to get the values out of them or else we’ll encounter deadlock again!
Example: sum := make(chan int)
The above example shows that the channel sum expects integer values to be placed into it.

This was a brief introduction to go-routines and channels; let’s jump ahead to solve a problem using go-routines and channels.

Problem statement: You have been given an integer array and you have to sum the elements of the array in one go-routine and print the sum one-by-one in a separate go-routine.

Here’s the solution to the problem:

package main
import "fmt"
func main() {
sum := make(chan int)
done := make(chan bool, 1)
arr := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
go add(arr, sum, done)
go print(sum, done)
<-done
}
func add(arr []int, sum chan int, done chan bool) {
adds := 0
for _, i := range arr {
adds += i
sum <- adds
}
done <- true
}
func print(sum chan int, done chan bool) {
for {
select {
case x := <- sum:
fmt.Println(x)
case <- done:
done <- true
break
}
}
}

As you can see I have used the add function as the go-routine to add up the array elements and the print function to print the sum. Here, channel sum is an unbuffered channel which takes care of passing of the sum of array elements from add to print go-routine. And I have used another channel (buffered) named done. You’re probably wondering why! That’s because as mentioned earlier, go routines are independent of the code block they’re called from so if I do not use the done channel, your program won’t print anything (you can try that out).
You can say that channel done is used here to wait for the go-routine(s) to complete the execution so we can see the output we desire!

That’s about all on this topic! Hope this is helpful.
Please provide your reviews or additions in the comments! Happy learning!

--

--