|
|
@@ -9,6 +9,9 @@ import (
|
|
|
"github.com/opencost/opencost/core/pkg/collections"
|
|
|
)
|
|
|
|
|
|
+// Runner is a function type that takes a single input and returns nothing.
|
|
|
+type Runner[T any] func(T)
|
|
|
+
|
|
|
// Worker is a transformation function from input type T to output type U.
|
|
|
type Worker[T any, U any] func(T) U
|
|
|
|
|
|
@@ -54,7 +57,7 @@ type queuedWorkerPool[T any, U any] struct {
|
|
|
type ordered[T any, U any] struct {
|
|
|
workPool WorkerPool[T, U]
|
|
|
results []U
|
|
|
- wg *sync.WaitGroup
|
|
|
+ wg sync.WaitGroup
|
|
|
count int
|
|
|
}
|
|
|
|
|
|
@@ -129,7 +132,6 @@ func NewOrderedGroup[T any, U any](pool WorkerPool[T, U], size int) WorkGroup[T,
|
|
|
return &ordered[T, U]{
|
|
|
workPool: pool,
|
|
|
results: make([]U, size),
|
|
|
- wg: new(sync.WaitGroup),
|
|
|
count: 0,
|
|
|
}
|
|
|
}
|
|
|
@@ -166,6 +168,96 @@ func (ow *ordered[T, U]) Wait() []U {
|
|
|
return ow.results
|
|
|
}
|
|
|
|
|
|
+// noResultGroup is a WorkGroup implementation which arbitrarily pushes inputs to
|
|
|
+// a runner pool to be executed concurrently. This group does not collect results.
|
|
|
+type noResultGroup[T any] struct {
|
|
|
+ workPool WorkerPool[T, struct{}]
|
|
|
+ wg sync.WaitGroup
|
|
|
+}
|
|
|
+
|
|
|
+// NewNoResultGroup creates a new WorkGroup implementation for processing a group of inputs concurrently. This
|
|
|
+// work group implementation does not collect results, and therefore, requires a worker pool with a struct{} output.
|
|
|
+func NewNoResultGroup[T any](pool WorkerPool[T, struct{}]) WorkGroup[T, struct{}] {
|
|
|
+ return &noResultGroup[T]{
|
|
|
+ workPool: pool,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Push adds a new input to the work group.
|
|
|
+func (ow *noResultGroup[T]) Push(input T) error {
|
|
|
+ onComplete := make(chan struct{})
|
|
|
+ err := ow.workPool.Run(input, onComplete)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ ow.wg.Add(1)
|
|
|
+
|
|
|
+ go func() {
|
|
|
+ defer close(onComplete)
|
|
|
+ defer ow.wg.Done()
|
|
|
+
|
|
|
+ <-onComplete
|
|
|
+ }()
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// Wait waits for all pending worker tasks to complete, then returns all the results.
|
|
|
+func (ow *noResultGroup[T]) Wait() []struct{} {
|
|
|
+ ow.wg.Wait()
|
|
|
+ return []struct{}{}
|
|
|
+}
|
|
|
+
|
|
|
+// collector is a WorkGroup implementation which collects non-nil results into the results slice
|
|
|
+// and ignores any nil results.
|
|
|
+type collector[T any, U any] struct {
|
|
|
+ workPool WorkerPool[T, *U]
|
|
|
+ resultLock sync.Mutex
|
|
|
+ results []*U
|
|
|
+ wg sync.WaitGroup
|
|
|
+}
|
|
|
+
|
|
|
+// NewCollectionGroup creates a new WorkGroup implementation for processing a group of inputs concurrently. The
|
|
|
+// collection group implementation will collect all non-nil results into the output slice. Thus, the worker pool
|
|
|
+// parameter requires the output type to be a pointer.
|
|
|
+func NewCollectionGroup[T any, U any](pool WorkerPool[T, *U]) WorkGroup[T, *U] {
|
|
|
+ return &collector[T, U]{
|
|
|
+ workPool: pool,
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Push adds a new input to the work group.
|
|
|
+func (ow *collector[T, U]) Push(input T) error {
|
|
|
+ onComplete := make(chan *U)
|
|
|
+ err := ow.workPool.Run(input, onComplete)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ ow.wg.Add(1)
|
|
|
+
|
|
|
+ go func() {
|
|
|
+ defer ow.wg.Done()
|
|
|
+ defer close(onComplete)
|
|
|
+
|
|
|
+ result := <-onComplete
|
|
|
+ if result != nil {
|
|
|
+ ow.resultLock.Lock()
|
|
|
+ ow.results = append(ow.results, result)
|
|
|
+ ow.resultLock.Unlock()
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// Wait waits for all pending worker tasks to complete, then returns all the results.
|
|
|
+func (ow *collector[T, U]) Wait() []*U {
|
|
|
+ ow.wg.Wait()
|
|
|
+ return ow.results
|
|
|
+}
|
|
|
+
|
|
|
// these constraints protect against the possibility of unexpected output from runtime.NumCPU()
|
|
|
const (
|
|
|
defaultMinWorkers = 4
|
|
|
@@ -190,10 +282,21 @@ func OptimalWorkerCountInRange(min int, max int) int {
|
|
|
return cores
|
|
|
}
|
|
|
|
|
|
-// ConcurrentDo runs a pool of workers which concurrently call the provided worker func on each input to get ordered
|
|
|
-// output corresponding to the inputs
|
|
|
+// ConcurrentDo runs a pool of N workers which concurrently call the provided worker func on each
|
|
|
+// input to get ordered output corresponding to the inputs. The total number of workers is determined
|
|
|
+// by the total number of CPUs available, bound to a range from 4-16.
|
|
|
func ConcurrentDo[T any, U any](worker Worker[T, U], inputs []T) []U {
|
|
|
- workerPool := NewWorkerPool(OptimalWorkerCount(), worker)
|
|
|
+ return ConcurrentDoWith(OptimalWorkerCount(), worker, inputs)
|
|
|
+}
|
|
|
+
|
|
|
+// ConcurrentDoWith runs a pool of workers of the specified size which concurrently call the provided worker func
|
|
|
+// on each input to get ordered output corresponding to the inputs. Size inputs < 1 will automatically be set to 1.
|
|
|
+func ConcurrentDoWith[T any, U any](size int, worker Worker[T, U], inputs []T) []U {
|
|
|
+ if size < 1 {
|
|
|
+ size = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ workerPool := NewWorkerPool(size, worker)
|
|
|
defer workerPool.Shutdown()
|
|
|
|
|
|
workGroup := NewOrderedGroup(workerPool, len(inputs))
|
|
|
@@ -203,3 +306,55 @@ func ConcurrentDo[T any, U any](worker Worker[T, U], inputs []T) []U {
|
|
|
|
|
|
return workGroup.Wait()
|
|
|
}
|
|
|
+
|
|
|
+// ConcurrentCollect runs a pool of N workers which concurrently call the provided worker func on each
|
|
|
+// input to get a result slice of non-nil outputs. The total number of workers is determined
|
|
|
+// by the total number of CPUs available, bound to a range from 4-16.
|
|
|
+func ConcurrentCollect[T any, U any](workerFunc Worker[T, *U], inputs []T) []*U {
|
|
|
+ return ConcurrentCollectWith(OptimalWorkerCount(), workerFunc, inputs)
|
|
|
+}
|
|
|
+
|
|
|
+// ConcurrentCollectWith runs a pool of workers of the specified size which concurrently call the provided worker
|
|
|
+// func on each input to get a result slice of non-nil outputs. Size inputs < 1 will automatically be set to 1.
|
|
|
+func ConcurrentCollectWith[T any, U any](size int, workerFunc Worker[T, *U], inputs []T) []*U {
|
|
|
+ if size < 1 {
|
|
|
+ size = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ workerPool := NewWorkerPool(size, workerFunc)
|
|
|
+ defer workerPool.Shutdown()
|
|
|
+
|
|
|
+ workGroup := NewCollectionGroup(workerPool)
|
|
|
+ for _, input := range inputs {
|
|
|
+ workGroup.Push(input)
|
|
|
+ }
|
|
|
+
|
|
|
+ return workGroup.Wait()
|
|
|
+}
|
|
|
+
|
|
|
+// ConcurrentRun runs a pool of N workers which concurrently call the provided runner func on each
|
|
|
+// input. The total number of workers is determined by the total number of CPUs available, bound to
|
|
|
+// a range from 4-16.
|
|
|
+func ConcurrentRun[T any](runner Runner[T], inputs []T) {
|
|
|
+ ConcurrentRunWith(OptimalWorkerCount(), runner, inputs)
|
|
|
+}
|
|
|
+
|
|
|
+// ConcurrentRunWith runs a pool of runners of the specified size which concurrently call the provided runner
|
|
|
+// func on each input. Size inputs < 1 will automatically be set to 1.
|
|
|
+func ConcurrentRunWith[T any](size int, runner Runner[T], inputs []T) {
|
|
|
+ if size < 1 {
|
|
|
+ size = 1
|
|
|
+ }
|
|
|
+
|
|
|
+ workerPool := NewWorkerPool(size, func(input T) (void struct{}) {
|
|
|
+ runner(input)
|
|
|
+ return
|
|
|
+ })
|
|
|
+
|
|
|
+ workGroup := NewNoResultGroup(workerPool)
|
|
|
+ for _, input := range inputs {
|
|
|
+ workGroup.Push(input)
|
|
|
+ }
|
|
|
+
|
|
|
+ workGroup.Wait()
|
|
|
+}
|