Go Channels
Communication pipes between goroutines
📡 What are Channels?
Channels are communication pipes that allow goroutines to send and receive values safely. They provide a way to synchronize goroutines and share data without explicit locks or condition variables.
// Basic channel example
package main
import "fmt"
func main() {
// Create a channel
ch := make(chan string)
// Send data in a goroutine
go func() {
ch <- "Hello from channel!"
}()
// Receive data
message := <-ch
fmt.Println(message)
}
Channel Types
Unbuffered
Synchronous communication
ch := make(chan int)
ch <- 42 // Blocks until received
value := <-ch
Buffered
Asynchronous with capacity
ch := make(chan int, 3)
ch <- 1 // Won't block
ch <- 2 // Won't block
ch <- 3 // Won't block
Send-only
Can only send values
func sender(ch chan<- int) {
ch <- 42
// Can't receive: <-ch
}
Receive-only
Can only receive values
func receiver(ch <-chan int) {
value := <-ch
// Can't send: ch <- 42
}
🔹 Creating and Using Channels
Channels are created with make() and used with <- operator:
package main
import (
"fmt"
"time"
)
func main() {
// Create unbuffered channel
messages := make(chan string)
// Send data in goroutine
go func() {
time.Sleep(1 * time.Second)
messages <- "Hello"
messages <- "World"
close(messages) // Close channel when done
}()
// Receive data
for msg := range messages {
fmt.Println("Received:", msg)
}
fmt.Println("Channel closed")
}
Output:
Received: Hello Received: World Channel closed
🔹 Buffered Channels
Buffered channels can hold multiple values without blocking:
package main
import "fmt"
func main() {
// Create buffered channel with capacity 3
ch := make(chan int, 3)
// Send values (won't block)
ch <- 1
ch <- 2
ch <- 3
fmt.Printf("Channel length: %d\n", len(ch))
fmt.Printf("Channel capacity: %d\n", cap(ch))
// Receive values
fmt.Println("Received:", <-ch)
fmt.Println("Received:", <-ch)
fmt.Println("Received:", <-ch)
}
Output:
Channel length: 3 Channel capacity: 3 Received: 1 Received: 2 Received: 3
🔹 Channel Directions
Restrict channels to send-only or receive-only for better design:
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go sender(ch)
receiver(ch)
}
// Send-only channel parameter
func sender(ch chan<- string) {
for i := 1; i <= 3; i++ {
ch <- fmt.Sprintf("Message %d", i)
time.Sleep(500 * time.Millisecond)
}
close(ch)
}
// Receive-only channel parameter
func receiver(ch <-chan string) {
for msg := range ch {
fmt.Println("Got:", msg)
}
}
Output:
Got: Message 1 Got: Message 2 Got: Message 3
🔹 Worker Pool Pattern
Use channels to implement a worker pool for concurrent processing:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
const numWorkers = 3
const numJobs = 9
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
// Start workers
var wg sync.WaitGroup
for w := 1; w <= numWorkers; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Send jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs)
// Wait for workers to finish
go func() {
wg.Wait()
close(results)
}()
// Collect results
for result := range results {
fmt.Printf("Result: %d\n", result)
}
}
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
time.Sleep(500 * time.Millisecond)
results <- job * 2
}
}