github.com/alphadose/itogami@v0.4.1-0.20221016160904-c25d0a36bfe7/pool_func.go (about) 1 package itogami 2 3 import ( 4 "sync" 5 "sync/atomic" 6 "unsafe" 7 ) 8 9 type ( 10 // a single slot for a worker in PoolWithFunc 11 slotFunc[T any] struct { 12 threadPtr unsafe.Pointer 13 data T 14 } 15 16 // PoolWithFunc is used for spawning workers for a single pre-defined function with myriad inputs 17 // useful for throughput bound cases 18 // has lower memory usage and allocs per op than the default Pool 19 // 20 // ( type -> func(T) {} ) where T is a generic parameter 21 PoolWithFunc[T any] struct { 22 currSize uint64 23 _p1 [cacheLinePadSize - unsafe.Sizeof(uint64(0))]byte 24 maxSize uint64 25 alloc func() any 26 free func(any) 27 task func(T) 28 _p2 [cacheLinePadSize - unsafe.Sizeof(uint64(0)) - 3*unsafe.Sizeof(func() {})]byte 29 top atomic.Pointer[dataItem[T]] 30 _p3 [cacheLinePadSize - unsafe.Sizeof(atomic.Pointer[dataItem[T]]{})]byte 31 } 32 ) 33 34 // NewPoolWithFunc returns a new PoolWithFunc 35 func NewPoolWithFunc[T any](size uint64, task func(T)) *PoolWithFunc[T] { 36 dataPool := sync.Pool{New: func() any { return new(dataItem[T]) }} 37 return &PoolWithFunc[T]{maxSize: size, task: task, alloc: dataPool.Get, free: dataPool.Put} 38 } 39 40 // Invoke invokes the pre-defined method in PoolWithFunc by assigning the data to an already existing worker 41 // or spawning a new worker given queue size is in limits 42 func (self *PoolWithFunc[T]) Invoke(value T) { 43 var s *slotFunc[T] 44 for { 45 if s = self.pop(); s != nil { 46 s.data = value 47 safe_ready(s.threadPtr) 48 return 49 } else if atomic.AddUint64(&self.currSize, 1) <= self.maxSize { 50 s = &slotFunc[T]{data: value} 51 go self.loopQ(s) 52 return 53 } else { 54 atomic.AddUint64(&self.currSize, uint64SubtractionConstant) 55 mcall(gosched_m) 56 } 57 } 58 } 59 60 // represents the infinite loop for a worker goroutine 61 func (self *PoolWithFunc[T]) loopQ(d *slotFunc[T]) { 62 d.threadPtr = GetG() 63 for { 64 self.task(d.data) 65 self.push(d) 66 mcall(fast_park) 67 } 68 } 69 70 // Stack implementation below for storing goroutine references 71 72 // a single node in the stack 73 type dataItem[T any] struct { 74 next atomic.Pointer[dataItem[T]] 75 value *slotFunc[T] 76 } 77 78 // pop pops value from the top of the stack 79 func (self *PoolWithFunc[T]) pop() (value *slotFunc[T]) { 80 var top, next *dataItem[T] 81 for { 82 top = self.top.Load() 83 if top == nil { 84 return 85 } 86 next = top.next.Load() 87 if self.top.CompareAndSwap(top, next) { 88 value = top.value 89 top.value = nil 90 top.next.Store(nil) 91 self.free(top) 92 return 93 } 94 } 95 } 96 97 // push pushes a value on top of the stack 98 func (self *PoolWithFunc[T]) push(v *slotFunc[T]) { 99 var ( 100 top *dataItem[T] 101 item = self.alloc().(*dataItem[T]) 102 ) 103 item.value = v 104 for { 105 top = self.top.Load() 106 item.next.Store(top) 107 if self.top.CompareAndSwap(top, item) { 108 return 109 } 110 } 111 }