Error Handling
Error Handling Strategies in Channel-based Concurrency
Error handling in concurrent Golang programs requires careful design to manage potential failures across multiple goroutines effectively.
Basic Error Channel Pattern
func processTask(task int) error {
if task < 0 {
return fmt.Errorf("invalid task: %d", task)
}
return nil
}
func main() {
tasks := make(chan int, 10)
errors := make(chan error, 10)
go func() {
for task := range tasks {
if err := processTask(task); err != nil {
errors <- err
}
}
close(errors)
}()
// Send tasks
tasks <- 1
tasks <- -1
close(tasks)
// Handle errors
for err := range errors {
fmt.Println("Error:", err)
}
}
Error Handling Patterns
1. Centralized Error Collection
graph LR
A[Goroutine 1] -->|Errors| B[Error Channel]
C[Goroutine 2] -->|Errors| B
D[Goroutine 3] -->|Errors| B
B --> E[Error Handler]
2. Context-based Error Propagation
func worker(ctx context.Context, jobs <-chan int, results chan<- int, errc chan<- error) {
for job := range jobs {
select {
case <-ctx.Done():
errc <- ctx.Err()
return
default:
if job < 0 {
errc <- fmt.Errorf("invalid job: %d", job)
continue
}
results <- job * 2
}
}
}
Error Handling Strategies
Strategy |
Description |
Pros |
Cons |
Error Channel |
Dedicated channel for errors |
Clear separation |
Overhead in management |
Context Cancellation |
Propagate errors and cancellation |
Flexible |
Complex implementation |
Panic and Recover |
Catch runtime errors |
Simple |
Not recommended for production |
Advanced Error Handling
Timeout and Error Combination
func processWithTimeout(timeout time.Duration) error {
done := make(chan bool)
errc := make(chan error)
go func() {
// Simulate work
time.Sleep(timeout + time.Second)
done <- true
}()
select {
case <-done:
return nil
case err := <-errc:
return err
case <-time.After(timeout):
return fmt.Errorf("operation timed out")
}
}
Best Practices for LabEx Developers
- Use dedicated error channels
- Implement graceful error handling
- Avoid blocking error channels
- Use context for complex error propagation
Error Propagation Flow
graph TD
A[Goroutine] --> B{Error Occurred?}
B -->|Yes| C[Error Channel]
B -->|No| D[Continue Execution]
C --> E[Central Error Handler]
Common Pitfalls
- Unbuffered error channels can cause goroutine leaks
- Ignoring errors can lead to silent failures
- Over-complicated error handling reduces code readability
Practical Example: Parallel Processing with Error Handling
func parallelProcess(inputs []int) ([]int, error) {
results := make(chan int, len(inputs))
errc := make(chan error, len(inputs))
var wg sync.WaitGroup
for _, input := range inputs {
wg.Add(1)
go func(val int) {
defer wg.Done()
if val < 0 {
errc <- fmt.Errorf("negative input: %d", val)
return
}
results <- val * 2
}(input)
}
go func() {
wg.Wait()
close(results)
close(errc)
}()
var processedResults []int
for result := range results {
processedResults = append(processedResults, result)
}
select {
case err := <-errc:
return nil, err
default:
return processedResults, nil
}
}
This comprehensive guide covers error handling techniques in channel-based concurrent Golang programming, providing developers with robust strategies for managing errors effectively.