Channels: Communication Between Goroutines
Channels are Go’s primary mechanism for safe communication between goroutines. They enable synchronization and data exchange.
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