Goroutines are Lightweight, Not Free
While a goroutine begins with only 2 to 4 KB of stack space compared to 1 to 2 MB for an operating system thread, unbounded spawning under high-throughput conditions will rapidly exhaust memory or file descriptors. Production services demand bounded concurrency.
Implementing a Clean Worker Pool
A worker pool decouples job producers from consumers using buffered channels and a synchronizing wait group:
package main
import (
"context"
"fmt"
"sync"
"time"
)
type Job struct {
ID int
Data string
}
func Worker(ctx context.Context, id int, jobs <-chan Job, results chan<- string, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case job, ok := <-jobs:
if !ok {
return
}
// Simulate work
time.Sleep(50 * time.Millisecond)
results <- fmt.Sprintf("worker %d processed job %d", id, job.ID)
}
}
}
Graceful Shutdown with Context Propagation
Always tie background processing to a top-level context.WithTimeout or signal.NotifyContext so in-flight requests finish cleanly without dropping connections during container redeployments.