github.com/fholzer/ordered-concurrently/v3@v3.0.0-20221001131746-406a6eece748/main.go (about) 1 package orderedconcurrently 2 3 import ( 4 "container/heap" 5 "context" 6 "sync" 7 ) 8 9 // Options options for Process 10 type Options struct { 11 PoolSize int 12 OutChannelBuffer int 13 } 14 15 // OrderedOutput is the output channel type from Process 16 type OrderedOutput struct { 17 Value interface{} 18 Remaining func() int 19 } 20 21 // WorkFunction interface 22 // type WorkFunction func(ctx context.Context) interface{} 23 type ProcessFunc func(interface{}) interface{} 24 type processFuncGenerator func(int) (ProcessFunc, error) 25 26 // Process processes work function based on input. 27 // It Accepts an WorkFunction read channel, work function and concurrent go routine pool size. 28 // It Returns an interface{} channel. 29 func Process(ctx context.Context, inputChan <-chan interface{}, processFuncGenerator processFuncGenerator, options *Options) (<-chan OrderedOutput, error) { 30 31 outputChan := make(chan OrderedOutput, options.OutChannelBuffer) 32 33 if options.PoolSize < 1 { 34 // Set a minimum number of processors 35 options.PoolSize = 1 36 } 37 38 processors := make([]ProcessFunc, options.PoolSize) 39 for i := 0; i < options.PoolSize; i++ { 40 var err error 41 processors[i], err = processFuncGenerator(i) 42 if err != nil { 43 return nil, err 44 } 45 } 46 47 go func() { 48 processChan := make(chan *processInput, options.PoolSize) 49 aggregatorChan := make(chan *processInput, options.PoolSize) 50 51 // Go routine to print data in order 52 go func() { 53 var current uint64 54 outputHeap := &processInputHeap{} 55 defer func() { 56 close(outputChan) 57 }() 58 remaining := func() int { 59 return outputHeap.Len() 60 } 61 for item := range aggregatorChan { 62 heap.Push(outputHeap, item) 63 for top, ok := outputHeap.Peek(); ok && top.order == current; { 64 outputChan <- OrderedOutput{Value: heap.Pop(outputHeap).(*processInput).value, Remaining: remaining} 65 current++ 66 } 67 } 68 69 for outputHeap.Len() > 0 { 70 outputChan <- OrderedOutput{Value: heap.Pop(outputHeap).(*processInput).value, Remaining: remaining} 71 } 72 }() 73 74 poolWg := sync.WaitGroup{} 75 poolWg.Add(options.PoolSize) 76 // Create a goroutine pool 77 for i := 0; i < options.PoolSize; i++ { 78 go func(process ProcessFunc) { 79 defer poolWg.Done() 80 for input := range processChan { 81 input.value = process(input.input) 82 aggregatorChan <- input 83 } 84 }(processors[i]) 85 } 86 87 go func() { 88 poolWg.Wait() 89 close(aggregatorChan) 90 }() 91 92 go func() { 93 defer func() { 94 close(processChan) 95 }() 96 var order uint64 97 for input := range inputChan { 98 processChan <- &processInput{input: input, order: order} 99 order++ 100 } 101 }() 102 }() 103 return outputChan, nil 104 }