A Deep Dive into Go Concurrency Patterns

Hoàng Ngô Anh Đức· 28/05/2026 02:12
A Deep Dive into Go Concurrency Patterns Cover

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

// Reader response

Comments

0 Comments

This article has no comments yet.

Name is limited to 60 characters and comment content to 1000 characters.

Hoàng Ngô Anh Đức

// 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.