awesome-patterns/concurrency/subtasks/divide_and_conquer/divide_and_conquer.go

105 lines
2.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package divide_and_conquer
import (
"fmt"
"reflect"
"runtime"
"time"
"github.com/davecgh/go-spew/spew"
)
func RunDivideAndConquer() {
type in struct {
a int
b int
}
type out struct {
source string
result int
}
evaluators := []Evaluator{
EvaluatorFunc(func(inV interface{}) (interface{}, error) {
i := inV.(in)
r := i.a + i.b
return out{"Plus", r}, nil
}),
EvaluatorFunc(func(inV interface{}) (interface{}, error) {
i := inV.(in)
r := i.a * i.b
return out{"Multi", r}, nil
}),
EvaluatorFunc(func(inV interface{}) (interface{}, error) {
i := inV.(in)
r := i.a - i.b
return out{"min", r}, nil
}),
EvaluatorFunc(func(inV interface{}) (interface{}, error) {
i := inV.(in)
r := i.a / i.b
return out{"divider", r}, nil
}),
}
r, errors := DivideAndConquer(in{2, 3}, evaluators, 10*time.Millisecond)
spew.Dump(r, errors)
}
type Evaluator interface {
Evaluate(data interface{}) (interface{}, error)
Name() string
}
type EvaluatorFunc func(interface{}) (interface{}, error)
func (ef EvaluatorFunc) Evaluate(in interface{}) (interface{}, error) {
return ef(in)
}
func (ef EvaluatorFunc) Name() string {
return runtime.FuncForPC(reflect.ValueOf(ef).Pointer()).Name()
}
func DivideAndConquer(data interface{}, evaluators []Evaluator, timeout time.Duration) ([]interface{}, []error) {
gather := make(chan interface{}, len(evaluators))
errors := make(chan error, len(evaluators))
for _, v := range evaluators {
go func(e Evaluator) {
// Why not just use an unbuffered channel? The answer is that we dont want to leak any goroutines.
// While the Go runtime is capable of handling thousands or hundreds of thousands of goroutines at a time,
// each goroutine does use some resources, so you dont want to leave them hanging around when
// you dont have to. If you do, a long-running Go program will start performing poorly
ch := make(chan interface{}, 1)
ech := make(chan error, 1)
go func() {
result, err := e.Evaluate(data)
if err != nil {
errors <- err
} else {
ch <- result
}
}()
select {
case r := <-ch:
gather <- r
case err := <-ech:
errors <- err
case <-time.After(timeout):
errors <- fmt.Errorf("%s timeout after %v on %v", e.Name(), timeout, data)
}
}(v)
}
out := make([]interface{}, 0, len(evaluators))
errs := make([]error, 0, len(evaluators))
for range evaluators {
select {
case r := <-gather:
out = append(out, r)
case e := <-errors:
errs = append(errs, e)
}
}
return out, errs
}