Skip to main content

Running Multiple Strategies in Parallel Using Goroutines in Go

I recently revisited a project I had worked on some time ago, where the system was required to take multiple parameters, query data from various sources, and then return a unified response. Reflecting on that project, I became curious about how I would approach building this process today. As a result, I decided to create a simple template to run multiple strategies concurrently.

A Simple Version

In this example, we’ll explore how to run multiple strategies in parallel using goroutines and channels. We’ll build a simple system that processes multiple strategies concurrently and returns a unified response from all of them.


package main

import (
	"context"
	"fmt"
	"sync"
)

func main() {
	// Create a context with a timeout
	ctx := context.Background()

	// Create a WaitGroup to synchronize goroutines
	var wg sync.WaitGroup

	// List of strategy names
	strategies := []Strategy{
		&strA{},
		&strB{},
	}

	// start a response channel
	responseCh := make(chan []byte, len(strategies))

	// Launch each strategy in a separate goroutine
	for _, str := range strategies {
		wg.Add(1)

		go func(ctx context.Context, s Strategy) {
			defer wg.Done()

			resp, err := s.Process(ctx)
			if err != nil {
				fmt.Println("Error processing strategy:", err)
				return
			}

			responseCh $lt;- resp
		}(ctx, str)
	}

	// Wait for all goroutines to finish
	wg.Wait()

	// Close the response channel
	close(responseCh)

	out := make([]byte, 0, len(strategies))
	for resp := range responseCh {
		out = append(out, resp...)
	}

	// Print the responses
	fmt.Println("All strategies finished")
	fmt.Println("Responses:", string(out))
}

// Strategy is an interface that contains the methods to process strategies
type Strategy interface {
	// Process processes the strategy
	Process(ctx context.Context) ([]byte, error)
}

// strA is a struct that contains the methods to process a dummy strategy
type strA struct{}

// Implements the strategy interface
func (s *strA) Process(ctx context.Context) ([]byte, error) {
	return []byte("A"), nil
}

// strB is a struct that contains the methods to process a dummy strategy
type strB struct{}

// Implements the strategy interface
func (s *strB) Process(ctx context.Context) ([]byte, error) {
	return []byte("B"), nil
}

Strategy Interface

I define a Strategy interface that outlines a Process method. Each strategy struct (in this case, strA and strB) implements this interface, allowing us to process them in a uniform way.

For simplicity, we define two strategies (strA and strB) that simulate some work and return dummy data. 

Conclusion

This example demonstrates how to run multiple strategies in parallel using goroutines and channels in Go. By using sync.WaitGroup to ensure all goroutines complete, and channels to collect the results, we can efficiently run concurrent processes and gather their output into a unified response. This approach can easily be extended to more complex strategies or integrated into larger systems. 



Comments

Popular posts from this blog

Simple Go HTTP Server Template

When working on various Go projects for clients, one recurring need is setting up a basic HTTP server. Whether it’s for health checks, APIs, or serving simple content, having a lightweight server template is essential. In this post, I’ll share a simple template I use to quickly spin up an HTTP server. This template can be extended to suit any project, and I’ll walk you through the key components. The Main Package In the main.go file, we initialize and start the server. I’ve also integrated graceful shutdown capabilities using the bsm/shutdown package to ensure that the server stops cleanly when interrupted. package main import ( "fmt" "log" "net/http" "../simple-server/internal/server" "github.com/bsm/shutdown" ) func main() { // Start the server fmt.Println("Starting server...") srv, err := server.New() if err != nil { log.Fatalln("failed to start server:", err) } defer func() { err := srv.Stop(...

Working with Rails Importmap and Sprockets

When starting a new Rails project, you may find yourself juggling different asset management tools. In my recent project, Rails came pre-configured with both: gem "sprockets-rails" gem "importmap-rails" I was keen to use `importmap-rails`, as it offers a modern, gem-free way to manage JavaScript dependencies. However, I was already familiar with `sprockets-rails` from previous projects, which made the mixed setup feel a bit confusing. Since my project uses Bootstrap 5 alongside Turbo and Stimulus, it took some trial and error to get everything working smoothly—especially in production. The Challenge: Importmap in Production According to the Importmap documentation , JavaScript files should be served directly from the `/app/javascript` folder. This works perfectly in development. However, in production, I noticed that the JavaScript files were not being correctly referenced, leading to missing assets and broken functionality. The solution? Precompiling the...