“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