Concurrency Patterns
Common Concurrency Patterns in Go
1. Worker Pool Pattern
package main
import (
"fmt"
"sync"
)
func worker(id int, jobs <-chan int, results chan<- int, wg *sync.WaitGroup) {
defer wg.Done()
for job := range jobs {
fmt.Printf("Worker %d processing job %d\n", id, job)
results <- job * 2
}
}
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
var wg sync.WaitGroup
// Create worker pool
for w := 1; w <= 3; w++ {
wg.Add(1)
go worker(w, jobs, results, &wg)
}
// Send jobs
for j := 1; j <= 5; j++ {
jobs <- j
}
close(jobs)
wg.Wait()
close(results)
// Collect results
for result := range results {
fmt.Println("Result:", result)
}
}
Concurrency Pattern Types
Pattern |
Description |
Use Case |
Worker Pool |
Distributes tasks among fixed number of workers |
Parallel processing |
Fan-Out/Fan-In |
Multiple goroutines producing, single goroutine consuming |
Data processing |
Pipeline |
Stages of processing connected by channels |
Data transformation |
Semaphore |
Limits concurrent access to resources |
Resource management |
Pipeline Pattern
package main
import "fmt"
func generator(nums ...int) <-chan int {
out := make(chan int)
go func() {
for _, n := range nums {
out <- n
}
close(out)
}()
return out
}
func square(in <-chan int) <-chan int {
out := make(chan int)
go func() {
for n := range in {
out <- n * n
}
close(out)
}()
return out
}
func main() {
// Pipeline: generate numbers -> square them
pipeline := square(generator(1, 2, 3, 4))
for v := range pipeline {
fmt.Println(v)
}
}
Concurrency Flow Visualization
graph TD
A[Input Data] --> B[Generator]
B --> C[Processing Stage 1]
C --> D[Processing Stage 2]
D --> E[Final Output]
Fan-Out/Fan-In Pattern
package main
import (
"fmt"
"sync"
)
func fanOut(ch <-chan int, out1, out2 chan<- int) {
for v := range ch {
out1 <- v
out2 <- v
}
close(out1)
close(out2)
}
func main() {
input := make(chan int)
output1 := make(chan int)
output2 := make(chan int)
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
fanOut(input, output1, output2)
}()
go func() {
for v := range output1 {
fmt.Println("Output 1:", v)
}
}()
go func() {
for v := range output2 {
fmt.Println("Output 2:", v)
}
}()
// Send inputs
input <- 1
input <- 2
input <- 3
close(input)
wg.Wait()
}
Best Practices
- Use channels for communication between goroutines
- Keep goroutines small and focused
- Avoid sharing memory, pass data through channels
- Use context for cancellation and timeouts
Note: LabEx encourages developers to experiment with these patterns to build robust concurrent applications.