Concurrent Patterns
Common Concurrent Design Patterns in Golang
Concurrent programming requires strategic approaches to manage complex interactions between goroutines efficiently.
1. Worker Pool Pattern
Manage a fixed number of workers processing tasks from a shared queue.
package main
import (
"fmt"
"sync"
)
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)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
var wg sync.WaitGroup
// Create worker pool
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Send jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
// Collect results
for result := range results {
fmt.Println("Result:", result)
}
}
Worker Pool Characteristics
Characteristic |
Description |
Scalability |
Limit concurrent workers |
Resource Control |
Prevent system overload |
Efficiency |
Reuse goroutines |
2. Fan-Out/Fan-In Pattern
Distribute work across multiple goroutines and collect results.
graph TD
A[Input] --> B[Distributor]
B --> C1[Worker 1]
B --> C2[Worker 2]
B --> C3[Worker 3]
C1 --> D[Aggregator]
C2 --> D
C3 --> D
D --> E[Final Result]
3. Select Statement for Concurrent Control
Handle multiple channel operations with flexible synchronization.
func fanIn(ch1, ch2 <-chan int) <-chan int {
c := make(chan int)
go func() {
for {
select {
case v := <-ch1:
c <- v
case v := <-ch2:
c <- v
}
}
}()
return c
}
4. Timeout and Context Management
Control long-running operations and prevent goroutine leaks.
func operationWithTimeout() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
select {
case <-performOperation():
fmt.Println("Operation completed")
case <-ctx.Done():
fmt.Println("Operation timed out")
}
}
Concurrent Pattern Categories
Category |
Purpose |
Synchronization |
Coordinate goroutine execution |
Resource Management |
Control concurrent access |
Communication |
Exchange data between goroutines |
Best Practices
- Use patterns to manage complexity
- Minimize shared state
- Prefer composition over inheritance
- Design for testability
- Avoid premature optimization
- Profile your concurrent code
- Understand goroutine overhead
Common Anti-Patterns
- Excessive goroutine creation
- Improper channel usage
- Neglecting synchronization
LabEx recommends a systematic approach to designing concurrent systems, focusing on clear communication and minimal shared state.
Advanced Techniques
- Semaphores
- Rate limiting
- Pipeline processing
- Graceful shutdown mechanisms
By mastering these patterns, developers can create robust, efficient, and scalable concurrent applications in Golang.