Channels in Go
Channels are Go’s built-in mechanism for communication between goroutines. They allow goroutines to safely send and receive data without explicit locks. In this lesson, you will learn how channels work, why they exist, and how to use them correctly.
Why Channels Exist
Goroutines run concurrently, but concurrency introduces a major challenge: safe data sharing.
Instead of sharing memory directly, Go encourages a different approach:
“Do not communicate by sharing memory; share memory by communicating.”
Channels implement this philosophy by allowing goroutines to exchange values safely.
What Is a Channel?
A channel is a typed conduit through which you can send and receive values.
Each channel has:
- A specific data type
- A sender
- A receiver
Creating a Channel
Channels are created using the make function.
ch := make(chan int)
This creates an unbuffered channel that can send and receive integers.
Sending and Receiving Data
Use the <- operator to send and receive values.
ch <- 10 // send
value := <-ch // receive
Send and receive operations block until the other side is ready.
Basic Channel Example
package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
ch <- "Hello from goroutine"
}()
message := <-ch
fmt.Println(message)
}
Here, the main goroutine waits until it receives data from the channel.
Blocking Behavior Explained
Channels synchronize goroutines automatically:
- Sending blocks until a receiver is ready
- Receiving blocks until a sender is ready
This eliminates many race conditions by design.
Real-World Example: Data Processing
Consider processing sensor readings from multiple devices.
func sensor(id int, ch chan int) {
ch <- id * 10
}
func main() {
ch := make(chan int)
for i := 1; i <= 3; i++ {
go sensor(i, ch)
}
for i := 1; i <= 3; i++ {
fmt.Println("Reading:", <-ch)
}
}
Each goroutine sends data back to the main routine safely.
Channel Direction (Send-Only & Receive-Only)
Channels can be restricted to one direction for better safety.
func sendData(ch chan<- int) {
ch <- 100
}
func receiveData(ch <-chan int) {
fmt.Println(<-ch)
}
This prevents accidental misuse of channels.
Closing a Channel
Channels can be closed to signal that no more data will be sent.
close(ch)
Closing is usually done by the sender, not the receiver.
Receiving from a Closed Channel
You can detect whether a channel is closed using the “comma ok” pattern.
value, ok := <-ch
if !ok {
fmt.Println("Channel closed")
}
Common Channel Mistakes
- Sending on a closed channel (panic)
- Forgetting to receive (deadlock)
- Closing a channel multiple times
- Using channels without goroutines
When to Use Channels
- Goroutine communication
- Work distribution
- Pipeline processing
- Event signaling
Practice Exercises
Exercise 1
Create a program where one goroutine sends five numbers into a channel and the main goroutine prints them.
Exercise 2
Modify the program to close the channel and detect when it is closed.
Key Takeaways
- Channels enable safe communication between goroutines
- They block by default, ensuring synchronization
- Closing channels signals completion
- Directional channels improve safety
What’s Next?
In the next lesson, you will learn about Buffered Channels and how they improve performance by allowing asynchronous communication.