Channels are the pipes that connect concurrent goroutines. They allow you to send values from one goroutine and receive them in another, providing a safe way to communicate and synchronize.
Creating Channels
Channels are created using make() and are typed by the values they convey:
package main
import "fmt"
func main() {
// Create a channel of strings
messages := make(chan string)
// Send a value into the channel from a goroutine
go func() { messages <- "ping" }()
// Receive the value from the channel
msg := <-messages
fmt.Println(msg) // Output: ping
}
Channel Syntax
Send Operation
Sends value into channel. This operation blocks until a receiver is ready.
Receive Operation
Receives a value from channel. This operation blocks until a value is available.
By default, sends and receives block until both the sender and receiver are ready. This property is called synchronization.
Blocking Behavior
Channels synchronize execution between goroutines:
messages := make(chan string)
go func() {
time.Sleep(1 * time.Second)
messages <- "ping"
}()
// This line blocks until the goroutine sends a value
msg := <-messages
fmt.Println(msg)
The receive operation <-messages waits for the send operation to complete.
Sending or receiving on an unbuffered channel from the same goroutine without another goroutine will cause a deadlock.
Common Deadlock Example
// DEADLOCK! Don't do this:
ch := make(chan string)
ch <- "value" // Blocks forever waiting for a receiver
msg := <-ch // Never reached
Channel Types
Channels can convey any type:
intChannel := make(chan int)
stringChannel := make(chan string)
structChannel := make(chan MyStruct)
errorChannel := make(chan error)
Practical Example: Goroutine Communication
func fetchData(url string, results chan<- string) {
// Simulate fetching data
data := "data from " + url
results <- data
}
func main() {
results := make(chan string)
go fetchData("https://api.example.com", results)
// Wait for and receive the result
data := <-results
fmt.Println(data)
}
Channel Operations Summary
| Operation | Syntax | Blocks Until |
|---|
| Send | ch <- v | Receiver ready |
| Receive | v := <-ch | Sender ready |
| Create | make(chan T) | N/A |
| Close | close(ch) | N/A |
Channels are first-class values - you can pass them as function parameters, return them from functions, and store them in data structures.
Receive with Status Check
value, ok := <-channel
if ok {
fmt.Println("Received:", value)
} else {
fmt.Println("Channel closed")
}
The second return value ok is false if the channel is closed and empty.
Best Practices
- Use channels for communication - Share memory by communicating, don’t communicate by sharing memory
- Close channels from the sender - Only the sender should close a channel
- Check channel status - Use the two-value receive form when necessary
- Prefer unbuffered channels - Unless you have a specific need for buffering
Common Patterns
Signal Channel
done := make(chan bool)
go func() {
// Do work
done <- true
}()
<-done // Wait for completion
Pipeline
func gen(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}