mirror of
https://github.com/crazybber/awesome-patterns.git
synced 2024-11-23 05:06:01 +03:00
105 lines
2.5 KiB
Go
105 lines
2.5 KiB
Go
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 don’t 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 don’t want to leave them hanging around when
|
||
// you don’t 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
|
||
}
|