Channels are Go’s primary mechanism for safe communication between goroutines. They enable synchronization and data exchange. For more context, see Go Installation Guide, Go Ecosystem Overview, Go Best Practices.
Channel Basics
Creating Channels
package main
import "fmt"
func main() {
// Unbuffered channel
ch := make(chan int)
// Buffered channel
buffered := make(chan int, 5)
// Channel of strings
strCh := make(chan string)
fmt.Println(ch, buffered, strCh)
}
Sending and Receiving
package main
import "fmt"
func main() {
ch := make(chan int)
// Send in goroutine
go func() {
ch <- 42
}()
// Receive
value := <-ch
fmt.Println(value) // 42
}
Unbuffered vs Buffered Channels
Unbuffered Channels
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
fmt.Println("Sending...")
ch <- 42
fmt.Println("Sent")
}()
time.Sleep(1 * time.Second)
fmt.Println("Receiving...")
value := <-ch
fmt.Println("Received:", value)
}
Buffered Channels
package main
import "fmt"
func main() {
// Buffered channel with capacity 2
ch := make(chan int, 2)
// Can send without blocking
ch <- 1
ch <- 2
// Receive
fmt.Println(<-ch) // 1
fmt.Println(<-ch) // 2
}
Channel Operations
Closing Channels
package main
import "fmt"
func main() {
ch := make(chan int, 3)
ch <- 1
ch <- 2
ch <- 3
close(ch)
// Receive from closed channel
for value := range ch {
fmt.Println(value)
}
}
Checking if Channel is Closed
package main
import "fmt"
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
close(ch)
// Check if channel is closed
value, ok := <-ch
fmt.Println(value, ok) // 0 false
value, ok = <-ch
fmt.Println(value, ok) // 0 false
}
Select Statement
Basic Select
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan string)
ch2 := make(chan string)
go func() {
time.Sleep(1 * time.Second)
ch1 <- "one"
}()
go func() {
time.Sleep(2 * time.Second)
ch2 <- "two"
}()
for i := 0; i < 2; i++ {
select {
case msg1 := <-ch1:
fmt.Println("Received:", msg1)
case msg2 := <-ch2:
fmt.Println("Received:", msg2)
}
}
}
Select with Default
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
select {
case value := <-ch:
fmt.Println("Received:", value)
default:
fmt.Println("No value available")
}
// Non-blocking send
select {
case ch <- 42:
fmt.Println("Sent")
default:
fmt.Println("Channel full")
}
}
Select with Timeout
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
select {
case value := <-ch:
fmt.Println("Received:", value)
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
}
}
Channel Patterns
Pipeline Pattern
package main
import "fmt"
func generate(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
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 := generate(1, 2, 3, 4, 5)
squares := square(nums)
for n := range squares {
fmt.Println(n)
}
}
Fan-Out/Fan-In Pattern
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 10)
results := make(chan int, 10)
// Fan-out: start workers
for i := 1; i <= 3; i++ {
go worker(i, jobs, results)
}
// Send jobs
for j := 1; j <= 10; j++ {
jobs <- j
}
close(jobs)
// Fan-in: collect results
for i := 0; i < 10; i++ {
fmt.Println(<-results)
}
}
Directional Channels
Send-Only Channels
package main
import "fmt"
func send(ch chan<- int) {
ch <- 42
}
func main() {
ch := make(chan int)
go send(ch)
value := <-ch
fmt.Println(value)
}
Receive-Only Channels
package main
import "fmt"
func receive(ch <-chan int) {
value := <-ch
fmt.Println(value)
}
func main() {
ch := make(chan int)
go func() {
ch <- 42
}()
receive(ch)
}
Best Practices
✅ Good: Close Channels Properly
// DO: Close channels from sender side
ch := make(chan int)
go func() {
ch <- 1
ch <- 2
close(ch)
}()
for value := range ch {
fmt.Println(value)
}
❌ Bad: Receive from Closed Channel
// DON'T: Receive from closed channel
ch := make(chan int)
close(ch)
value := <-ch // Panic!
✅ Good: Use Directional Channels
// DO: Use directional channels in function signatures
func send(ch chan<- int) { ch <- 42 }
func receive(ch <-chan int) { <-ch }
✅ Good: Use Select for Timeouts
// DO: Use select with timeout
select {
case value := <-ch:
fmt.Println(value)
case <-time.After(1 * time.Second):
fmt.Println("Timeout")
}
Summary
Channels provide:
- Safe communication between goroutines
- Synchronization primitives
- Buffering for decoupling
- Select statement for multiplexing
- Directional channels for type safety
These features enable safe concurrent programming in Go.
Comments