Channels
Channels are Go's pipes for communication between goroutines. They provide a safe way to send and receive values, following the principle: "Don't communicate by sharing memory; share memory by communicating."
What is a Channel?
A channel is a typed conduit through which you can send and receive values:
Click Run to execute your code
ch <- value- Send value to channelvalue := <-ch- Receive value from channelclose(ch)- Close channel (sender only)value, ok := <-ch- Receive with closed check
Unbuffered vs Buffered Channels
Click Run to execute your code
Channel Direction
You can restrict channels to send-only or receive-only:
// Send-only channel
func sender(ch chan<- int) {
ch <- 42 // OK
// val := <-ch // Error: receive from send-only
}
// Receive-only channel
func receiver(ch <-chan int) {
val := <-ch // OK
// ch <- 42 // Error: send to receive-only
}
func main() {
ch := make(chan int) // Bidirectional
go sender(ch) // Converts to send-only
receiver(ch) // Converts to receive-only
}
Closing Channels
Click Run to execute your code
- Only the sender should close a channel
- Sending to a closed channel causes panic
- Receiving from a closed channel returns zero value
- Closing is optional (for signaling completion)
Range over Channels
func producer(ch chan<- int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // Signal completion
}
func main() {
ch := make(chan int)
go producer(ch)
// Range automatically stops when channel closed
for value := range ch {
fmt.Println(value)
}
}
Common Channel Patterns
1. Pipeline Pattern
func generator() <-chan int {
ch := make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch)
}()
return ch
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
nums := generator()
squares := square(nums)
for result := range squares {
fmt.Println(result)
}
}
2. Fan-Out, Fan-In
// Fan-out: Multiple workers read from same channel
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
results <- job * 2 // Process job
}
}
// Fan-in: Merge multiple channels into one
func merge(channels ...<-chan int) <-chan int {
out := make(chan int)
var wg sync.WaitGroup
for _, ch := range channels {
wg.Add(1)
go func(c <-chan int) {
defer wg.Done()
for v := range c {
out <- v
}
}(ch)
}
go func() {
wg.Wait()
close(out)
}()
return out
}
Common Mistakes
1. Deadlock - no receiver
// โ Wrong - deadlock!
ch := make(chan int)
ch <- 42 // Blocks forever (no receiver)
// โ
Correct - use goroutine
ch := make(chan int)
go func() {
ch <- 42
}()
value := <-ch
2. Sending to closed channel
// โ Wrong - panic!
ch := make(chan int)
close(ch)
ch <- 42 // panic: send on closed channel
// โ
Correct - only sender closes
// Never close from receiver side
3. Not closing channels in range
// โ Wrong - range never exits
func producer(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
}
// Forgot to close!
}
// โ
Correct - close when done
func producer(ch chan int) {
for i := 0; i < 5; i++ {
ch <- i
}
close(ch) // Signal completion
}
Exercise: Worker Pool
Task: Create a worker pool using channels.
Requirements:
- Create 3 workers that process jobs concurrently
- Send 10 jobs through a jobs channel
- Collect results through a results channel
- Print which worker processed each job
Show Solution
package main
import (
"fmt"
"time"
)
func worker(id int, jobs <-chan int, results chan<- string) {
for job := range jobs {
fmt.Printf("Worker %d started job %d\n", id, job)
time.Sleep(time.Millisecond * 500) // Simulate work
result := fmt.Sprintf("Worker %d completed job %d (result: %d)", id, job, job*2)
results <- result
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan string, numJobs)
// Start workers
for w := 1; w <= numWorkers; w++ {
go worker(w, jobs, results)
}
// Send jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Collect results
for a := 1; a <= numJobs; a++ {
fmt.Println(<-results)
}
}
Summary
- Channels provide safe communication between goroutines
- Unbuffered channels synchronize sender and receiver
- Buffered channels decouple with capacity
- Directional channels restrict send/receive operations
- close() signals no more values (sender only)
- range loops until channel is closed
- Patterns: pipeline, fan-out, fan-in
What's Next?
Now that you understand channels, you're ready to learn about Select & Patterns. The select statement lets you work with multiple channels, enabling powerful concurrent patterns!
Enjoying these tutorials?