Concurrency Best Practices
Fundamental Concurrency Principles
1. Channels Over Shared Memory
Prefer communication through channels instead of sharing memory between goroutines.
func worker(jobs <-chan int, results chan<- int) {
for job := range jobs {
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for w := 0; w < 3; w++ {
go worker(jobs, results)
}
}
2. Design for Concurrency
graph TD
A[Concurrent Design] --> B[Separate Concerns]
A --> C[Minimize Shared State]
A --> D[Use Channels for Communication]
Concurrency Patterns
Worker Pool Pattern
func workerPool(workerCount int, jobs <-chan Task) <-chan Result {
results := make(chan Result, workerCount)
var wg sync.WaitGroup
for i := 0; i < workerCount; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
result := processTask(job)
results <- result
}
}()
}
go func() {
wg.Wait()
close(results)
}()
return results
}
Concurrency Anti-Patterns
Anti-Pattern |
Description |
Risks |
Excessive Goroutines |
Creating too many goroutines |
Resource exhaustion |
Unbounded Concurrency |
Unlimited parallel execution |
Performance degradation |
Shared Mutable State |
Direct memory sharing |
Race conditions |
Error Handling in Concurrent Code
Graceful Error Propagation
func processWithTimeout(ctx context.Context, task Task) error {
errChan := make(chan error, 1)
go func() {
errChan <- performTask(task)
}()
select {
case err := <-errChan:
return err
case <-ctx.Done():
return ctx.Err()
}
}
Concurrency Optimization Strategies
- Limit Concurrent Operations
- Use Buffered Channels
- Leverage Context for Cancellation
Advanced Concurrency Techniques
Context-Based Cancellation
func fetchData(ctx context.Context) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
resultChan := make(chan []byte, 1)
errChan := make(chan error, 1)
go func() {
data, err := performRemoteCall()
if err != nil {
errChan <- err
return
}
resultChan <- data
}()
select {
case result := <-resultChan:
return result, nil
case err := <-errChan:
return nil, err
case <-ctx.Done():
return nil, ctx.Err()
}
}
Synchronization Primitives Comparison
Primitive |
Use Case |
Overhead |
Complexity |
Mutex |
Exclusive Access |
Low |
Simple |
Channel |
Communication |
Medium |
Moderate |
Atomic |
Simple Operations |
Lowest |
Simplest |
LabEx Concurrency Recommendations
At LabEx, we advocate for:
- Designing concurrent systems with clear boundaries
- Minimizing shared state
- Using channels for inter-goroutine communication
- Implementing robust error handling
Key Takeaways
- Prefer channels over shared memory
- Design for concurrency from the start
- Use context for timeout and cancellation
- Limit and manage goroutine lifecycle
- Handle errors gracefully