A Deep Dive into Go Concurrency Patterns
Go is famous for its first-class support for **concurrency**. Rather than relying on heavy OS threads (which consume megabytes of memory), Go uses **goroutines**—lightweight threads managed by the Go runtime scheduler that start with only **2KB of stack space**.
However, concurrent programming is inherently complex. Writing race conditions or leaking goroutines is incredibly easy if you do not master synchronization primitives. Let's explore production-grade concurrency patterns!
### 1. The Worker Pool Pattern
When processing massive workloads (like scraping URLs or resizing images), running unlimited goroutines can exhaust server memory or CPU resources.
A **Worker Pool** limits the concurrency, running a fixed number of workers to consume from a shared channel:
```go
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for j := range jobs {
fmt.Printf("Worker %d processing job %d
", id, j)
results <- j * 2
}
}
func main() {
const numJobs = 10
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
// Spin up 3 concurrent workers
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Feed jobs
for j := 1; j <= numJobs; j++ {
jobs <- j
}
close(jobs) // Signal that no more jobs are coming
wg.Wait()
close(results)
for r := range results {
fmt.Printf("Result: %d
", r)
}
}
```
### 2. Orchestrating Errors with 'golang.org/x/sync/errgroup'
Often, we want to start multiple concurrent operations, wait for them to finish, and return an error if **any** of them fail. 'sync.WaitGroup' cannot handle this easily, but 'errgroup' does so elegantly:
```go
package main
import (
"context"
"errors"
"golang.org/x/sync/errgroup"
)
func FetchAllData() error {
g, ctx := errgroup.WithContext(context.Background())
g.Go(func() error {
// Fetch users from API (simulated)
return nil
})
g.Go(func() error {
// Fetch inventory database (simulated)
if errOccurred := true; errOccurred {
return errors.New("database connection timeout")
}
return nil
})
// Wait blocks until both finish. Returns the first error.
if err := g.Wait(); err != nil {
return err
}
return nil
}
```
By leveraging 'errgroup', context cancellation is automatically propagated to all parallel routines if one of them fails, preventing orphaned or hanging routines!
// Read next
Related articles
Building Resilient Microservices with Go and gRPC
Discover the architecture secrets behind high-throughput microservices using Go and gRPC. Learn about serialization effi...
Modern Headless CMS Architectures: Best Practices
Understand the decoupled web architecture. Explore how headless CMS systems power rapid multi-channel content delivery w...
Mastering SQLite for Production Web Applications
Think SQLite is just a toy database? Think again. Learn how to configure WAL mode, handle locking, and scale SQLite to m...
// Reader response
Comments
This article has no comments yet.
// Author
Hoàng Ngô Anh Đức
Senior Full-Stack Engineer & Software Architect
Tôi là một kỹ sư phần mềm giàu kinh nghiệm chuyên thiết kế và xây dựng các hệ thống web hiện đại, scalable backend sử dụng Go, Vue.js, TypeScript và kiến trúc đám mây Cloud. Đam mê chia sẻ kiến thức kỹ thuật và tối ưu hiệu năng phần mềm.