Concurrent Programming Patterns with Go Channels
Go channels provide a powerful and flexible way to implement concurrent programming patterns. This section explores some common patterns that leverage the capabilities of Go channels to solve various concurrency challenges.
Producer-Consumer Pattern
The producer-consumer pattern is a classic concurrency pattern where one or more producers generate data and send it to a channel, while one or more consumers receive and process the data from the same channel.
// Producer
func producer(out chan int) {
for i := 0; i < 10; i++ {
out <- i
}
close(out)
}
// Consumer
func consumer(in chan int) {
for value := range in {
fmt.Println("Consumed:", value)
}
}
func main() {
channel := make(chan int)
go producer(channel)
consumer(channel)
}
In this example, the producer generates integers and sends them to the channel, while the consumer reads the values from the channel and prints them.
Worker Pool Pattern
The worker pool pattern involves a pool of worker goroutines that process tasks from a shared work queue (channel). This pattern helps distribute work among multiple workers and can improve overall throughput.
// Worker
func worker(wg *sync.WaitGroup, tasks <-chan int) {
defer wg.Done()
for task := range tasks {
// Process the task
fmt.Println("Processed task:", task)
}
}
func main() {
tasks := make(chan int, 100)
var wg sync.WaitGroup
// Start workers
for i := 0; i < 5; i++ {
wg.Add(1)
go worker(&wg, tasks)
}
// Send tasks to the channel
for i := 0; i < 20; i++ {
tasks <- i
}
close(tasks)
// Wait for all workers to finish
wg.Wait()
}
In this example, the main goroutine creates a pool of worker goroutines and sends tasks to a shared channel. The workers process the tasks from the channel, and the sync.WaitGroup
ensures that the main goroutine waits for all workers to complete before exiting.
Fan-in/Fan-out Pattern
The fan-in/fan-out pattern involves multiple goroutines (the "fan-out" part) sending data to a single channel, which is then read by another goroutine (the "fan-in" part). This pattern can be used to distribute work and aggregate results.
// Fan-out
func generateNumbers(out chan int) {
for i := 0; i < 5; i++ {
out <- i
}
close(out)
}
// Fan-in
func sumNumbers(in <-chan int) int {
var sum int
for num := range in {
sum += num
}
return sum
}
func main() {
channel := make(chan int)
go generateNumbers(channel)
result := sumNumbers(channel)
fmt.Println("Sum:", result)
}
In this example, the generateNumbers
function sends numbers to a channel, while the sumNumbers
function reads from the same channel and calculates the sum of the numbers.
By understanding and applying these concurrent programming patterns with Go channels, you can write efficient and scalable concurrent applications that leverage the power of Go's concurrency primitives.