“Channels are a typed conduit through which you can send and receive values with the channel operator, <-
“. This is the definition of channels in the Golang documentation but what exactly does that mean?
First, you can look at channels as some form of portal between two worlds, where you throw something through it from world A and it appears in world B.
Let’s take an example;
Imagine, we’re in a multiverse. We’re in WorldA and trying to send message to our other self in WorldB that our nemesis is coming.
We first create a channel using the make function. This will take chan
as an argument to specify that it is a channel and the type will be the type of data we want to pass around. So we’ll create a channel that takes a string, “Our nemesis is coming”
and pass the data through the portal. To pass a data through a portal, you use the expression: <-
. The expression is like an arrow and the arrow direction is the direction of the data flow.
package main import "fmt" func main(){ // Create our channel which we are naming portal portal := make(chan string) // This is the message we're sending to our other self in WorldB msg := "Our nemesis is coming" // Message goes through portal portal <- msg // Message is received by our other self in World B worldBSelf := <- portal // We're going to print our message to see if everything is working well. fmt.Println(worldBSelf) }
Now when we run our code, we realise we have an error like the one below.
Output
❯ go run main.go fatal error: all goroutines are asleep - deadlock! goroutine 1 [chan send]: main.main() Go/channels/main.go:9 +0x48 exit status 2
So what is wrong? As I said earlier, think of a channel as a portal, it is just a conduit, just a passage, so if data is going in, then it’s coming out at the same time. This means our WorldBSelf
needs to also be at the portal at the exact moment we’re sending the data through the portal so as to receive the data, otherwise we can’t send the data. Thus, both sending and receiving of data has to happen concurrently otherwise it fails and when we talk about concurrency in Golang, then we’re talking about goroutines
. Let’s make some changes to our code.
package main import "fmt" func main(){ // Create our channel which we are naming portal portal := make(chan string) // This is the data we're sending to our other self in WorldB msg := "Our nemesis is coming" go func() { // Data goes through portal portal <- msg }() // Data is received by our other self in World B worldBSelf := <- portal // We're going to print our data to see if everything is working well. fmt.Println(worldBSelf) }
Output
Our nemesis is coming
What we’ve done now is to turn the sending of our data into a goroutine so it runs concurrently with the receiving of our data. Now when we run our data, everything should be ok because our WorldBSelf
is also present at the portal at the exact time we’re sending the data so he can receive it.
Channels are a good way to get data from goroutines as a goroutine is another thread that is running concurrently with the main thread. You can get data from the goroutine to your main thread through channels.
Let’s go through a practical example. Let’s say you’re trying to find the sum of elements in an array.
You can divide the array into two or three, spin up goroutines to find the sum of the individual sub arrays then get the result from each goroutine and add them together to get the total sum of the array.
package main import "fmt" // calculate the sum of elements in an array func calculateSum(numbers []int, channel chan int) { sum := 0 for _, v := range numbers { sum += v } channel <- sum } func main(){ // Create channel result:= make(chan int) array := []int{1,2,3,4,5,6,7,8,9,10} // Divide arrays into sub arrays firstSubArray, secondSubArray := array[:len(array)/2], array[len(array)/2:] // start goroutines go calculateSum(firstSubArray, result) go calculateSum(secondSubArray, result) // get result from goroutines x := <- result y := <- result // calculate total sum of the results from individual subarrays totalSum := x + y fmt.Printf("x: %d, y: %d, total sum: %d\n", x, y, totalSum) }
Output
X: 40, y: 15, total sum: 55
We divided our array into two sub arrays, we then put our calculateSum function into two goroutines, passing our sub arrays and channel as arguments. The result of those calculations are then passed through our channel to be received in the main thread and the total sum calculated.
Buffered Channels
Remember when I said we can’t send data through channels if we’re not sending and receiving the data concurrently? Technically, we can, with buffered channels. Buffered channel is just a channel that can temporarily store data. Look at it this way, since you and your WorldBSelf
can’t always be at the same place at the same time to transfer data, you found a more asynchronous way to do that. You found out there is a locker that is also a portal to WorldB, which means whatever you put in that locker, your WorldBSelf
can access that artefact when they are ready.
We can go to our very first code and add a buffer size to our channels to create this lockerPortal
.
package main import "fmt" func main(){ // Create our channel which we are naming portal lockerPortal := make(chan string, 1) // Add buffer size to channel // This is the message we're sending to our other self in WorldB msg := "Our nemesis is coming" // Message goes through portal lockerPortal <- msg // Message is received by our other self in World B worldBSelf := <- lockerPortal // We're going to print our message to see if everything is working well. fmt.Println(worldBSelf) }
With these changes, our code should run ok. Note that the buffered size provided denotes the quantity of data the channel can take. A buffer size of 1 means only one data can be passed through the channel at a time.
We can try this by adding another message to the lockerPortal before reading our first data.
package main import "fmt" func main(){ // Create our channel which we are naming portal lockerPortal := make(chan string, 1) // Add buffer size to channel // This is the message we're sending to our other self in WorldB msg1 := "Our nemesis is coming" // renamed to msg1 msg2 := "He is already in your World" // new code // Message goes through portal lockerPortal <- msg1 //renamed to msg1 lockerPortal <- msg2 // new code // Message is received by our other self in World B worldBSelf := <- lockerPortal worldBSelf := <- lockerPortal // We're going to print our message to see if everything is working well. fmt.Println(worldBSelf) }
When we run this we should have a deadlock error and the error is on the line we tried to add msg2
because we had exceeded our buffer size. If we rearrange the lines and put msg2
in our lockerPortal
after receiving msg1
, then it should work fine.
package main import "fmt" func main(){ // Create our channel which we are naming portal lockerPortal := make(chan string, 1) // This is the message we're sending to our other self in WorldB msg1 := "Our nemesis is coming" msg2 := "He is already in your World" // Send msg1 and receive it lockerPortal <- msg1 worldBSelf := <- lockerPortal fmt.Println(worldBSelf) // Send msg2 and receive it lockerPortal <- msg2 worldBSelf := <- lockerPortal fmt.Println(worldBSelf) }
Output
Our nemesis is coming He is already in your World
There are more concepts in Golang channels like closing channels and using select statements but I hope this was a good introduction to channels.
Top comments (4)
What would be a real-world example use case for this?
It will be a great use case for any asynchronous task you would want to do as channels will be a great way to pass data from those Goroutines. Let's say you're trying to make a request to an API for some data but the API is paginated. You can create requests to all the individual pages and put them in goroutines to make multiple requests concurrently to all the individual pages to get your data but when you do that then you're not expecting a return at the moment so you can't get return values from go routines. Thus, you can create a channel and pass the data through it so you can aggregate your data. You're basically saying, go on and do your own thing and when you done just pass the data through this channel so I can use it.
Great read man.
Thank you