Advanced concurrency patterns in Go involve techniques that enable efficient coordination and synchronization of concurrent tasks. These patterns can help you design high-performance, concurrent applications. In this tutorial, we'll explore some advanced concurrency patterns in Go with examples.
1. Worker Pool:
A worker pool is a common pattern for parallelizing tasks. It involves a fixed number of worker goroutines that pick up and process jobs from a queue.
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 {
result := job * 2
results <- result
}
}
func main() {
const numJobs = 10
const numWorkers = 3
jobs := make(chan int, numJobs)
results := make(chan int, numJobs)
var wg sync.WaitGroup
for i := 1; i <= numWorkers; i++ {
wg.Add(1)
go worker(i, jobs, results, &wg)
}
for i := 1; i <= numJobs; i++ {
jobs <- i
}
close(jobs)
go func() {
wg.Wait()
close(results)
}()
for result := range results {
fmt.Println(result)
}
}
2. Pipeline:
The pipeline pattern is useful for processing data in stages, where each stage is performed by a goroutine.
package main
import (
"fmt"
)
func main() {
numbers := []int{1, 2, 3, 4, 5}
input := make(chan int)
output := make(chan int)
go func() {
for _, n := range numbers {
input <- n
}
close(input)
}()
go func() {
for n := range input {
output <- n * 2
}
close(output)
}()
for result := range output {
fmt.Println(result)
}
}
3. Pub-Sub (Publish-Subscribe):
The pub-sub pattern involves multiple subscribers receiving events or data published by a publisher. Channels can be used to implement this pattern.
package main
import (
"fmt"
"sync"
)
type Event struct {
Data string
}
func main() {
var wg sync.WaitGroup
ch := make(chan Event, 10)
for i := 0; i < 3; i++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for event := range ch {
fmt.Printf("Subscriber %d received event: %s\n", id, event.Data)
}
}(i)
}
publishers := 2
for i := 0; i < publishers; i++ {
go func(id int) {
for j := 0; j < 5; j++ {
event := Event{Data: fmt.Sprintf("Event %d from publisher %d", j, id)}
ch <- event
}
}(i)
}
wg.Wait()
close(ch)
}
4. Context and Cancellation:
The context package in Go allows you to gracefully cancel and manage goroutines. This is useful for terminating tasks when they are no longer needed.
package main
import (
"context"
"fmt"
"time"
)
func worker(ctx context.Context) {
for {
select {
case <-ctx.Done():
fmt.Println("Worker is shutting down.")
return
default:
fmt.Println("Worker is processing.")
time.Sleep(1 * time.Second)
}
}
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
go worker(ctx)
select {
case <-ctx.Done():
fmt.Println("Main is done.")
}
}
These advanced concurrency patterns in Go offer powerful ways to handle concurrent tasks efficiently and effectively. Depending on your application's requirements, you can choose and adapt these patterns to build robust and high-performance concurrent applications.