Summary: in this tutorial, you will learn how to use Go channels to allow goroutines to communicate with each other.
Introduction to Go channels
In Go, channels are used to pass data between goroutines. In other words, goroutines share data by communicating with each other via channels.
Creating channels
To create a channel, you use the var
keyword, followed by the channel name, the chan
keyword, and the data type of the channel:
var channelName chan type
Code language: Go (go)
For example:
var ch chan string
Code language: Go (go)
In this example, we define a channel ch
with the type string
. The ch
channel allows goroutines to send and receive strings only.
Alternatively, you can use the built-in make()
function to create a channel:
ch := make(chan string)
Code language: Go (go)
Sending data to channels
To send data to a channel, you use the <-
operator:
channel <- data
Code language: Go (go)
For example, the following shows how to send the message "Hi"
to the channel ch
:
ch <- "Hi"
Code language: Go (go)
If you attempt to send a value that is not a string to the ch
channel, you’ll encounter a compilation error.
Receiving data from channels
To receive data from a channel, you use the <-
operator:
<- channel
Code language: Go (go)
The following example shows how to receive a string message from a channel:
mesage := <- channel
Code language: Go (go)
Receiving data from a channel will block the goroutine until the message is received.
Additionally, you can use a for range loop to read data from a channel:
for data := range channel {
// process the data
}
Code language: Go (go)
Putting it all together
Here’s the complete program that demonstrates how to use a channel to exchange data between goroutines:
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hi"
}()
message := <-ch
fmt.Println(message)
}
Code language: Go (go)
How it works.
First, create a channel ch
with the type of string
:
ch := make(chan string)
Code language: Go (go)
Second, spawn a new goroutine that sends the message "Hi"
to the ch
channel:
go func() {
ch <- "Hi"
}()
Code language: Go (go)
Third, receive the message
from the channel ch
in the main goroutine:
message := <-ch
Code language: Go (go)
Note that the main goroutine will not complete until it receives the message
from the ch
channel.
Finally, display the message on the screen:
fmt.Println(message)
Code language: Go (go)
In the following modified version of the program, the goroutine sends data to the channel after 3 seconds. Therefore, the main
goroutine will wait for the same amount of time before receiving the data:
package main
import (
"fmt"
"time"
)
func main() {
fmt.Println("Starting the program...")
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second)
ch <- "Hi"
}()
message := <-ch
fmt.Println(message)
}
Code language: Go (go)
Output:
Starting the program...
Hi
Code language: Go (go)
The message Hi
appears after 3 seconds after the program starts.
Deadlocks
The following program causes a deadlock that you may encounter when start working with channels:
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hi"
}()
message := <-ch
fmt.Println(message)
// Error: deadlock
message = <-ch
fmt.Println(message)
}
Code language: Go (go)
Output:
Hi
fatal error: all goroutines are asleep - deadlock!
Code language: Go (go)
In this example:
- First, a goroutine sends the message
"Hi"
to the channel. - Second, the
main
goroutine receives and prints the message. - Third, the main goroutine attempts to receive more data from the channel. However, no more data is sent to the channel, and the second receiving operation blocks indefinitely, causing a deadlock.
To prevent deadlocks, you should ensure every send has a matching receipt. In other words, there should be a corresponding receipt for every message you send, and vice versa.
A practical example of Go channels
The following example shows how to check the status of a list of URLs
concurrently:
package main
import (
"fmt"
"net/http"
)
func check(url string, ch chan string) {
_, err := http.Get(url)
if err != nil {
ch <- fmt.Sprintf("The %s is down.", url)
} else {
ch <- fmt.Sprintf("The %s is up.", url)
}
}
func main() {
urls := []string{
"https://google.com",
"https://go.dev",
"https://abcdef.test/",
}
ch := make(chan string)
for _, url := range urls {
go check(url, ch)
}
for i := 0; i < len(urls); i++ {
fmt.Println(<-ch)
}
}
Code language: Go (go)
How it works.
First, define a function that accepts a URL to check and a channel to send data to once the checking completes:
func check(url string, ch chan string) {
_, err := http.Get(url)
if err != nil {
ch <- fmt.Sprintf("The %s is down.", url)
} else {
ch <- fmt.Sprintf("The %s is up.", url)
}
}
Code language: Go (go)
Next, declare and initialize a list of URLs
to check:
urls := []string{
"https://google.com",
"https://go.dev",
"https://abcdef.test/",
}
Code language: Go (go)
Then, create a channel to exchange the messages:
ch := make(chan string)
Code language: Go (go)
After that, spawn goroutines to check the URLs
concurrently:
for _, url := range urls {
go check(url, ch)
}
Code language: Go (go)
Finally, receive the URL status from the ch channel in the main goroutine:
for i := 0; i < len(urls); i++ {
fmt.Println(<-ch)
}
Code language: Go (go)
Summary
- Use channels to send and receive data between goroutines.
- Use the
channel <- data
syntax to send data to a channel. - Use the
data := <- channel
to receive data from a channel. - Ensure the number of sends and receives are matched to avoid deadlocks.